You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4894 lines
190 KiB

  1. """Test date/time type.
  2. See http://www.zope.org/Members/fdrake/DateTimeWiki/TestCases
  3. """
  4. from test.support import is_resource_enabled
  5. import itertools
  6. import bisect
  7. import copy
  8. import decimal
  9. import sys
  10. import os
  11. import pickle
  12. import random
  13. import struct
  14. import unittest
  15. from array import array
  16. from operator import lt, le, gt, ge, eq, ne, truediv, floordiv, mod
  17. from test import support
  18. import datetime as datetime_module
  19. from datetime import MINYEAR, MAXYEAR
  20. from datetime import timedelta
  21. from datetime import tzinfo
  22. from datetime import time
  23. from datetime import timezone
  24. from datetime import date, datetime
  25. import time as _time
  26. # Needed by test_datetime
  27. import _strptime
  28. #
  29. pickle_choices = [(pickle, pickle, proto)
  30. for proto in range(pickle.HIGHEST_PROTOCOL + 1)]
  31. assert len(pickle_choices) == pickle.HIGHEST_PROTOCOL + 1
  32. # An arbitrary collection of objects of non-datetime types, for testing
  33. # mixed-type comparisons.
  34. OTHERSTUFF = (10, 34.5, "abc", {}, [], ())
  35. # XXX Copied from test_float.
  36. INF = float("inf")
  37. NAN = float("nan")
  38. #############################################################################
  39. # module tests
  40. class TestModule(unittest.TestCase):
  41. def test_constants(self):
  42. datetime = datetime_module
  43. self.assertEqual(datetime.MINYEAR, 1)
  44. self.assertEqual(datetime.MAXYEAR, 9999)
  45. def test_name_cleanup(self):
  46. if '_Pure' in self.__class__.__name__:
  47. self.skipTest('Only run for Fast C implementation')
  48. datetime = datetime_module
  49. names = set(name for name in dir(datetime)
  50. if not name.startswith('__') and not name.endswith('__'))
  51. allowed = set(['MAXYEAR', 'MINYEAR', 'date', 'datetime',
  52. 'datetime_CAPI', 'time', 'timedelta', 'timezone',
  53. 'tzinfo'])
  54. self.assertEqual(names - allowed, set([]))
  55. def test_divide_and_round(self):
  56. if '_Fast' in self.__class__.__name__:
  57. self.skipTest('Only run for Pure Python implementation')
  58. dar = datetime_module._divide_and_round
  59. self.assertEqual(dar(-10, -3), 3)
  60. self.assertEqual(dar(5, -2), -2)
  61. # four cases: (2 signs of a) x (2 signs of b)
  62. self.assertEqual(dar(7, 3), 2)
  63. self.assertEqual(dar(-7, 3), -2)
  64. self.assertEqual(dar(7, -3), -2)
  65. self.assertEqual(dar(-7, -3), 2)
  66. # ties to even - eight cases:
  67. # (2 signs of a) x (2 signs of b) x (even / odd quotient)
  68. self.assertEqual(dar(10, 4), 2)
  69. self.assertEqual(dar(-10, 4), -2)
  70. self.assertEqual(dar(10, -4), -2)
  71. self.assertEqual(dar(-10, -4), 2)
  72. self.assertEqual(dar(6, 4), 2)
  73. self.assertEqual(dar(-6, 4), -2)
  74. self.assertEqual(dar(6, -4), -2)
  75. self.assertEqual(dar(-6, -4), 2)
  76. #############################################################################
  77. # tzinfo tests
  78. class FixedOffset(tzinfo):
  79. def __init__(self, offset, name, dstoffset=42):
  80. if isinstance(offset, int):
  81. offset = timedelta(minutes=offset)
  82. if isinstance(dstoffset, int):
  83. dstoffset = timedelta(minutes=dstoffset)
  84. self.__offset = offset
  85. self.__name = name
  86. self.__dstoffset = dstoffset
  87. def __repr__(self):
  88. return self.__name.lower()
  89. def utcoffset(self, dt):
  90. return self.__offset
  91. def tzname(self, dt):
  92. return self.__name
  93. def dst(self, dt):
  94. return self.__dstoffset
  95. class PicklableFixedOffset(FixedOffset):
  96. def __init__(self, offset=None, name=None, dstoffset=None):
  97. FixedOffset.__init__(self, offset, name, dstoffset)
  98. def __getstate__(self):
  99. return self.__dict__
  100. class _TZInfo(tzinfo):
  101. def utcoffset(self, datetime_module):
  102. return random.random()
  103. class TestTZInfo(unittest.TestCase):
  104. def test_refcnt_crash_bug_22044(self):
  105. tz1 = _TZInfo()
  106. dt1 = datetime(2014, 7, 21, 11, 32, 3, 0, tz1)
  107. with self.assertRaises(TypeError):
  108. dt1.utcoffset()
  109. def test_non_abstractness(self):
  110. # In order to allow subclasses to get pickled, the C implementation
  111. # wasn't able to get away with having __init__ raise
  112. # NotImplementedError.
  113. useless = tzinfo()
  114. dt = datetime.max
  115. self.assertRaises(NotImplementedError, useless.tzname, dt)
  116. self.assertRaises(NotImplementedError, useless.utcoffset, dt)
  117. self.assertRaises(NotImplementedError, useless.dst, dt)
  118. def test_subclass_must_override(self):
  119. class NotEnough(tzinfo):
  120. def __init__(self, offset, name):
  121. self.__offset = offset
  122. self.__name = name
  123. self.assertTrue(issubclass(NotEnough, tzinfo))
  124. ne = NotEnough(3, "NotByALongShot")
  125. self.assertIsInstance(ne, tzinfo)
  126. dt = datetime.now()
  127. self.assertRaises(NotImplementedError, ne.tzname, dt)
  128. self.assertRaises(NotImplementedError, ne.utcoffset, dt)
  129. self.assertRaises(NotImplementedError, ne.dst, dt)
  130. def test_normal(self):
  131. fo = FixedOffset(3, "Three")
  132. self.assertIsInstance(fo, tzinfo)
  133. for dt in datetime.now(), None:
  134. self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
  135. self.assertEqual(fo.tzname(dt), "Three")
  136. self.assertEqual(fo.dst(dt), timedelta(minutes=42))
  137. def test_pickling_base(self):
  138. # There's no point to pickling tzinfo objects on their own (they
  139. # carry no data), but they need to be picklable anyway else
  140. # concrete subclasses can't be pickled.
  141. orig = tzinfo.__new__(tzinfo)
  142. self.assertIs(type(orig), tzinfo)
  143. for pickler, unpickler, proto in pickle_choices:
  144. green = pickler.dumps(orig, proto)
  145. derived = unpickler.loads(green)
  146. self.assertIs(type(derived), tzinfo)
  147. def test_pickling_subclass(self):
  148. # Make sure we can pickle/unpickle an instance of a subclass.
  149. offset = timedelta(minutes=-300)
  150. for otype, args in [
  151. (PicklableFixedOffset, (offset, 'cookie')),
  152. (timezone, (offset,)),
  153. (timezone, (offset, "EST"))]:
  154. orig = otype(*args)
  155. oname = orig.tzname(None)
  156. self.assertIsInstance(orig, tzinfo)
  157. self.assertIs(type(orig), otype)
  158. self.assertEqual(orig.utcoffset(None), offset)
  159. self.assertEqual(orig.tzname(None), oname)
  160. for pickler, unpickler, proto in pickle_choices:
  161. green = pickler.dumps(orig, proto)
  162. derived = unpickler.loads(green)
  163. self.assertIsInstance(derived, tzinfo)
  164. self.assertIs(type(derived), otype)
  165. self.assertEqual(derived.utcoffset(None), offset)
  166. self.assertEqual(derived.tzname(None), oname)
  167. def test_issue23600(self):
  168. DSTDIFF = DSTOFFSET = timedelta(hours=1)
  169. class UKSummerTime(tzinfo):
  170. """Simple time zone which pretends to always be in summer time, since
  171. that's what shows the failure.
  172. """
  173. def utcoffset(self, dt):
  174. return DSTOFFSET
  175. def dst(self, dt):
  176. return DSTDIFF
  177. def tzname(self, dt):
  178. return 'UKSummerTime'
  179. tz = UKSummerTime()
  180. u = datetime(2014, 4, 26, 12, 1, tzinfo=tz)
  181. t = tz.fromutc(u)
  182. self.assertEqual(t - t.utcoffset(), u)
  183. class TestTimeZone(unittest.TestCase):
  184. def setUp(self):
  185. self.ACDT = timezone(timedelta(hours=9.5), 'ACDT')
  186. self.EST = timezone(-timedelta(hours=5), 'EST')
  187. self.DT = datetime(2010, 1, 1)
  188. def test_str(self):
  189. for tz in [self.ACDT, self.EST, timezone.utc,
  190. timezone.min, timezone.max]:
  191. self.assertEqual(str(tz), tz.tzname(None))
  192. def test_repr(self):
  193. datetime = datetime_module
  194. for tz in [self.ACDT, self.EST, timezone.utc,
  195. timezone.min, timezone.max]:
  196. # test round-trip
  197. tzrep = repr(tz)
  198. self.assertEqual(tz, eval(tzrep))
  199. def test_class_members(self):
  200. limit = timedelta(hours=23, minutes=59)
  201. self.assertEqual(timezone.utc.utcoffset(None), ZERO)
  202. self.assertEqual(timezone.min.utcoffset(None), -limit)
  203. self.assertEqual(timezone.max.utcoffset(None), limit)
  204. def test_constructor(self):
  205. self.assertIs(timezone.utc, timezone(timedelta(0)))
  206. self.assertIsNot(timezone.utc, timezone(timedelta(0), 'UTC'))
  207. self.assertEqual(timezone.utc, timezone(timedelta(0), 'UTC'))
  208. for subminute in [timedelta(microseconds=1), timedelta(seconds=1)]:
  209. tz = timezone(subminute)
  210. self.assertNotEqual(tz.utcoffset(None) % timedelta(minutes=1), 0)
  211. # invalid offsets
  212. for invalid in [timedelta(1, 1), timedelta(1)]:
  213. self.assertRaises(ValueError, timezone, invalid)
  214. self.assertRaises(ValueError, timezone, -invalid)
  215. with self.assertRaises(TypeError): timezone(None)
  216. with self.assertRaises(TypeError): timezone(42)
  217. with self.assertRaises(TypeError): timezone(ZERO, None)
  218. with self.assertRaises(TypeError): timezone(ZERO, 42)
  219. with self.assertRaises(TypeError): timezone(ZERO, 'ABC', 'extra')
  220. def test_inheritance(self):
  221. self.assertIsInstance(timezone.utc, tzinfo)
  222. self.assertIsInstance(self.EST, tzinfo)
  223. def test_utcoffset(self):
  224. dummy = self.DT
  225. for h in [0, 1.5, 12]:
  226. offset = h * HOUR
  227. self.assertEqual(offset, timezone(offset).utcoffset(dummy))
  228. self.assertEqual(-offset, timezone(-offset).utcoffset(dummy))
  229. with self.assertRaises(TypeError): self.EST.utcoffset('')
  230. with self.assertRaises(TypeError): self.EST.utcoffset(5)
  231. def test_dst(self):
  232. self.assertIsNone(timezone.utc.dst(self.DT))
  233. with self.assertRaises(TypeError): self.EST.dst('')
  234. with self.assertRaises(TypeError): self.EST.dst(5)
  235. def test_tzname(self):
  236. self.assertEqual('UTC', timezone.utc.tzname(None))
  237. self.assertEqual('UTC', timezone(ZERO).tzname(None))
  238. self.assertEqual('UTC-05:00', timezone(-5 * HOUR).tzname(None))
  239. self.assertEqual('UTC+09:30', timezone(9.5 * HOUR).tzname(None))
  240. self.assertEqual('UTC-00:01', timezone(timedelta(minutes=-1)).tzname(None))
  241. self.assertEqual('XYZ', timezone(-5 * HOUR, 'XYZ').tzname(None))
  242. # Sub-minute offsets:
  243. self.assertEqual('UTC+01:06:40', timezone(timedelta(0, 4000)).tzname(None))
  244. self.assertEqual('UTC-01:06:40',
  245. timezone(-timedelta(0, 4000)).tzname(None))
  246. self.assertEqual('UTC+01:06:40.000001',
  247. timezone(timedelta(0, 4000, 1)).tzname(None))
  248. self.assertEqual('UTC-01:06:40.000001',
  249. timezone(-timedelta(0, 4000, 1)).tzname(None))
  250. with self.assertRaises(TypeError): self.EST.tzname('')
  251. with self.assertRaises(TypeError): self.EST.tzname(5)
  252. def test_fromutc(self):
  253. with self.assertRaises(ValueError):
  254. timezone.utc.fromutc(self.DT)
  255. with self.assertRaises(TypeError):
  256. timezone.utc.fromutc('not datetime')
  257. for tz in [self.EST, self.ACDT, Eastern]:
  258. utctime = self.DT.replace(tzinfo=tz)
  259. local = tz.fromutc(utctime)
  260. self.assertEqual(local - utctime, tz.utcoffset(local))
  261. self.assertEqual(local,
  262. self.DT.replace(tzinfo=timezone.utc))
  263. def test_comparison(self):
  264. self.assertNotEqual(timezone(ZERO), timezone(HOUR))
  265. self.assertEqual(timezone(HOUR), timezone(HOUR))
  266. self.assertEqual(timezone(-5 * HOUR), timezone(-5 * HOUR, 'EST'))
  267. with self.assertRaises(TypeError): timezone(ZERO) < timezone(ZERO)
  268. self.assertIn(timezone(ZERO), {timezone(ZERO)})
  269. self.assertTrue(timezone(ZERO) != None)
  270. self.assertFalse(timezone(ZERO) == None)
  271. def test_aware_datetime(self):
  272. # test that timezone instances can be used by datetime
  273. t = datetime(1, 1, 1)
  274. for tz in [timezone.min, timezone.max, timezone.utc]:
  275. self.assertEqual(tz.tzname(t),
  276. t.replace(tzinfo=tz).tzname())
  277. self.assertEqual(tz.utcoffset(t),
  278. t.replace(tzinfo=tz).utcoffset())
  279. self.assertEqual(tz.dst(t),
  280. t.replace(tzinfo=tz).dst())
  281. def test_pickle(self):
  282. for tz in self.ACDT, self.EST, timezone.min, timezone.max:
  283. for pickler, unpickler, proto in pickle_choices:
  284. tz_copy = unpickler.loads(pickler.dumps(tz, proto))
  285. self.assertEqual(tz_copy, tz)
  286. tz = timezone.utc
  287. for pickler, unpickler, proto in pickle_choices:
  288. tz_copy = unpickler.loads(pickler.dumps(tz, proto))
  289. self.assertIs(tz_copy, tz)
  290. def test_copy(self):
  291. for tz in self.ACDT, self.EST, timezone.min, timezone.max:
  292. tz_copy = copy.copy(tz)
  293. self.assertEqual(tz_copy, tz)
  294. tz = timezone.utc
  295. tz_copy = copy.copy(tz)
  296. self.assertIs(tz_copy, tz)
  297. def test_deepcopy(self):
  298. for tz in self.ACDT, self.EST, timezone.min, timezone.max:
  299. tz_copy = copy.deepcopy(tz)
  300. self.assertEqual(tz_copy, tz)
  301. tz = timezone.utc
  302. tz_copy = copy.deepcopy(tz)
  303. self.assertIs(tz_copy, tz)
  304. #############################################################################
  305. # Base class for testing a particular aspect of timedelta, time, date and
  306. # datetime comparisons.
  307. class HarmlessMixedComparison:
  308. # Test that __eq__ and __ne__ don't complain for mixed-type comparisons.
  309. # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a
  310. # legit constructor.
  311. def test_harmless_mixed_comparison(self):
  312. me = self.theclass(1, 1, 1)
  313. self.assertFalse(me == ())
  314. self.assertTrue(me != ())
  315. self.assertFalse(() == me)
  316. self.assertTrue(() != me)
  317. self.assertIn(me, [1, 20, [], me])
  318. self.assertIn([], [me, 1, 20, []])
  319. def test_harmful_mixed_comparison(self):
  320. me = self.theclass(1, 1, 1)
  321. self.assertRaises(TypeError, lambda: me < ())
  322. self.assertRaises(TypeError, lambda: me <= ())
  323. self.assertRaises(TypeError, lambda: me > ())
  324. self.assertRaises(TypeError, lambda: me >= ())
  325. self.assertRaises(TypeError, lambda: () < me)
  326. self.assertRaises(TypeError, lambda: () <= me)
  327. self.assertRaises(TypeError, lambda: () > me)
  328. self.assertRaises(TypeError, lambda: () >= me)
  329. #############################################################################
  330. # timedelta tests
  331. class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
  332. theclass = timedelta
  333. def test_constructor(self):
  334. eq = self.assertEqual
  335. td = timedelta
  336. # Check keyword args to constructor
  337. eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
  338. milliseconds=0, microseconds=0))
  339. eq(td(1), td(days=1))
  340. eq(td(0, 1), td(seconds=1))
  341. eq(td(0, 0, 1), td(microseconds=1))
  342. eq(td(weeks=1), td(days=7))
  343. eq(td(days=1), td(hours=24))
  344. eq(td(hours=1), td(minutes=60))
  345. eq(td(minutes=1), td(seconds=60))
  346. eq(td(seconds=1), td(milliseconds=1000))
  347. eq(td(milliseconds=1), td(microseconds=1000))
  348. # Check float args to constructor
  349. eq(td(weeks=1.0/7), td(days=1))
  350. eq(td(days=1.0/24), td(hours=1))
  351. eq(td(hours=1.0/60), td(minutes=1))
  352. eq(td(minutes=1.0/60), td(seconds=1))
  353. eq(td(seconds=0.001), td(milliseconds=1))
  354. eq(td(milliseconds=0.001), td(microseconds=1))
  355. def test_computations(self):
  356. eq = self.assertEqual
  357. td = timedelta
  358. a = td(7) # One week
  359. b = td(0, 60) # One minute
  360. c = td(0, 0, 1000) # One millisecond
  361. eq(a+b+c, td(7, 60, 1000))
  362. eq(a-b, td(6, 24*3600 - 60))
  363. eq(b.__rsub__(a), td(6, 24*3600 - 60))
  364. eq(-a, td(-7))
  365. eq(+a, td(7))
  366. eq(-b, td(-1, 24*3600 - 60))
  367. eq(-c, td(-1, 24*3600 - 1, 999000))
  368. eq(abs(a), a)
  369. eq(abs(-a), a)
  370. eq(td(6, 24*3600), a)
  371. eq(td(0, 0, 60*1000000), b)
  372. eq(a*10, td(70))
  373. eq(a*10, 10*a)
  374. eq(a*10, 10*a)
  375. eq(b*10, td(0, 600))
  376. eq(10*b, td(0, 600))
  377. eq(b*10, td(0, 600))
  378. eq(c*10, td(0, 0, 10000))
  379. eq(10*c, td(0, 0, 10000))
  380. eq(c*10, td(0, 0, 10000))
  381. eq(a*-1, -a)
  382. eq(b*-2, -b-b)
  383. eq(c*-2, -c+-c)
  384. eq(b*(60*24), (b*60)*24)
  385. eq(b*(60*24), (60*b)*24)
  386. eq(c*1000, td(0, 1))
  387. eq(1000*c, td(0, 1))
  388. eq(a//7, td(1))
  389. eq(b//10, td(0, 6))
  390. eq(c//1000, td(0, 0, 1))
  391. eq(a//10, td(0, 7*24*360))
  392. eq(a//3600000, td(0, 0, 7*24*1000))
  393. eq(a/0.5, td(14))
  394. eq(b/0.5, td(0, 120))
  395. eq(a/7, td(1))
  396. eq(b/10, td(0, 6))
  397. eq(c/1000, td(0, 0, 1))
  398. eq(a/10, td(0, 7*24*360))
  399. eq(a/3600000, td(0, 0, 7*24*1000))
  400. # Multiplication by float
  401. us = td(microseconds=1)
  402. eq((3*us) * 0.5, 2*us)
  403. eq((5*us) * 0.5, 2*us)
  404. eq(0.5 * (3*us), 2*us)
  405. eq(0.5 * (5*us), 2*us)
  406. eq((-3*us) * 0.5, -2*us)
  407. eq((-5*us) * 0.5, -2*us)
  408. # Issue #23521
  409. eq(td(seconds=1) * 0.123456, td(microseconds=123456))
  410. eq(td(seconds=1) * 0.6112295, td(microseconds=611229))
  411. # Division by int and float
  412. eq((3*us) / 2, 2*us)
  413. eq((5*us) / 2, 2*us)
  414. eq((-3*us) / 2.0, -2*us)
  415. eq((-5*us) / 2.0, -2*us)
  416. eq((3*us) / -2, -2*us)
  417. eq((5*us) / -2, -2*us)
  418. eq((3*us) / -2.0, -2*us)
  419. eq((5*us) / -2.0, -2*us)
  420. for i in range(-10, 10):
  421. eq((i*us/3)//us, round(i/3))
  422. for i in range(-10, 10):
  423. eq((i*us/-3)//us, round(i/-3))
  424. # Issue #23521
  425. eq(td(seconds=1) / (1 / 0.6112295), td(microseconds=611229))
  426. # Issue #11576
  427. eq(td(999999999, 86399, 999999) - td(999999999, 86399, 999998),
  428. td(0, 0, 1))
  429. eq(td(999999999, 1, 1) - td(999999999, 1, 0),
  430. td(0, 0, 1))
  431. def test_disallowed_computations(self):
  432. a = timedelta(42)
  433. # Add/sub ints or floats should be illegal
  434. for i in 1, 1.0:
  435. self.assertRaises(TypeError, lambda: a+i)
  436. self.assertRaises(TypeError, lambda: a-i)
  437. self.assertRaises(TypeError, lambda: i+a)
  438. self.assertRaises(TypeError, lambda: i-a)
  439. # Division of int by timedelta doesn't make sense.
  440. # Division by zero doesn't make sense.
  441. zero = 0
  442. self.assertRaises(TypeError, lambda: zero // a)
  443. self.assertRaises(ZeroDivisionError, lambda: a // zero)
  444. self.assertRaises(ZeroDivisionError, lambda: a / zero)
  445. self.assertRaises(ZeroDivisionError, lambda: a / 0.0)
  446. self.assertRaises(TypeError, lambda: a / '')
  447. @support.requires_IEEE_754
  448. def test_disallowed_special(self):
  449. a = timedelta(42)
  450. self.assertRaises(ValueError, a.__mul__, NAN)
  451. self.assertRaises(ValueError, a.__truediv__, NAN)
  452. def test_basic_attributes(self):
  453. days, seconds, us = 1, 7, 31
  454. td = timedelta(days, seconds, us)
  455. self.assertEqual(td.days, days)
  456. self.assertEqual(td.seconds, seconds)
  457. self.assertEqual(td.microseconds, us)
  458. def test_total_seconds(self):
  459. td = timedelta(days=365)
  460. self.assertEqual(td.total_seconds(), 31536000.0)
  461. for total_seconds in [123456.789012, -123456.789012, 0.123456, 0, 1e6]:
  462. td = timedelta(seconds=total_seconds)
  463. self.assertEqual(td.total_seconds(), total_seconds)
  464. # Issue8644: Test that td.total_seconds() has the same
  465. # accuracy as td / timedelta(seconds=1).
  466. for ms in [-1, -2, -123]:
  467. td = timedelta(microseconds=ms)
  468. self.assertEqual(td.total_seconds(), td / timedelta(seconds=1))
  469. def test_carries(self):
  470. t1 = timedelta(days=100,
  471. weeks=-7,
  472. hours=-24*(100-49),
  473. minutes=-3,
  474. seconds=12,
  475. microseconds=(3*60 - 12) * 1e6 + 1)
  476. t2 = timedelta(microseconds=1)
  477. self.assertEqual(t1, t2)
  478. def test_hash_equality(self):
  479. t1 = timedelta(days=100,
  480. weeks=-7,
  481. hours=-24*(100-49),
  482. minutes=-3,
  483. seconds=12,
  484. microseconds=(3*60 - 12) * 1000000)
  485. t2 = timedelta()
  486. self.assertEqual(hash(t1), hash(t2))
  487. t1 += timedelta(weeks=7)
  488. t2 += timedelta(days=7*7)
  489. self.assertEqual(t1, t2)
  490. self.assertEqual(hash(t1), hash(t2))
  491. d = {t1: 1}
  492. d[t2] = 2
  493. self.assertEqual(len(d), 1)
  494. self.assertEqual(d[t1], 2)
  495. def test_pickling(self):
  496. args = 12, 34, 56
  497. orig = timedelta(*args)
  498. for pickler, unpickler, proto in pickle_choices:
  499. green = pickler.dumps(orig, proto)
  500. derived = unpickler.loads(green)
  501. self.assertEqual(orig, derived)
  502. def test_compare(self):
  503. t1 = timedelta(2, 3, 4)
  504. t2 = timedelta(2, 3, 4)
  505. self.assertEqual(t1, t2)
  506. self.assertTrue(t1 <= t2)
  507. self.assertTrue(t1 >= t2)
  508. self.assertFalse(t1 != t2)
  509. self.assertFalse(t1 < t2)
  510. self.assertFalse(t1 > t2)
  511. for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
  512. t2 = timedelta(*args) # this is larger than t1
  513. self.assertTrue(t1 < t2)
  514. self.assertTrue(t2 > t1)
  515. self.assertTrue(t1 <= t2)
  516. self.assertTrue(t2 >= t1)
  517. self.assertTrue(t1 != t2)
  518. self.assertTrue(t2 != t1)
  519. self.assertFalse(t1 == t2)
  520. self.assertFalse(t2 == t1)
  521. self.assertFalse(t1 > t2)
  522. self.assertFalse(t2 < t1)
  523. self.assertFalse(t1 >= t2)
  524. self.assertFalse(t2 <= t1)
  525. for badarg in OTHERSTUFF:
  526. self.assertEqual(t1 == badarg, False)
  527. self.assertEqual(t1 != badarg, True)
  528. self.assertEqual(badarg == t1, False)
  529. self.assertEqual(badarg != t1, True)
  530. self.assertRaises(TypeError, lambda: t1 <= badarg)
  531. self.assertRaises(TypeError, lambda: t1 < badarg)
  532. self.assertRaises(TypeError, lambda: t1 > badarg)
  533. self.assertRaises(TypeError, lambda: t1 >= badarg)
  534. self.assertRaises(TypeError, lambda: badarg <= t1)
  535. self.assertRaises(TypeError, lambda: badarg < t1)
  536. self.assertRaises(TypeError, lambda: badarg > t1)
  537. self.assertRaises(TypeError, lambda: badarg >= t1)
  538. def test_str(self):
  539. td = timedelta
  540. eq = self.assertEqual
  541. eq(str(td(1)), "1 day, 0:00:00")
  542. eq(str(td(-1)), "-1 day, 0:00:00")
  543. eq(str(td(2)), "2 days, 0:00:00")
  544. eq(str(td(-2)), "-2 days, 0:00:00")
  545. eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
  546. eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
  547. eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
  548. "-210 days, 23:12:34")
  549. eq(str(td(milliseconds=1)), "0:00:00.001000")
  550. eq(str(td(microseconds=3)), "0:00:00.000003")
  551. eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
  552. microseconds=999999)),
  553. "999999999 days, 23:59:59.999999")
  554. def test_repr(self):
  555. name = 'datetime.' + self.theclass.__name__
  556. self.assertEqual(repr(self.theclass(1)),
  557. "%s(days=1)" % name)
  558. self.assertEqual(repr(self.theclass(10, 2)),
  559. "%s(days=10, seconds=2)" % name)
  560. self.assertEqual(repr(self.theclass(-10, 2, 400000)),
  561. "%s(days=-10, seconds=2, microseconds=400000)" % name)
  562. self.assertEqual(repr(self.theclass(seconds=60)),
  563. "%s(seconds=60)" % name)
  564. self.assertEqual(repr(self.theclass()),
  565. "%s(0)" % name)
  566. self.assertEqual(repr(self.theclass(microseconds=100)),
  567. "%s(microseconds=100)" % name)
  568. self.assertEqual(repr(self.theclass(days=1, microseconds=100)),
  569. "%s(days=1, microseconds=100)" % name)
  570. self.assertEqual(repr(self.theclass(seconds=1, microseconds=100)),
  571. "%s(seconds=1, microseconds=100)" % name)
  572. def test_roundtrip(self):
  573. for td in (timedelta(days=999999999, hours=23, minutes=59,
  574. seconds=59, microseconds=999999),
  575. timedelta(days=-999999999),
  576. timedelta(days=-999999999, seconds=1),
  577. timedelta(days=1, seconds=2, microseconds=3)):
  578. # Verify td -> string -> td identity.
  579. s = repr(td)
  580. self.assertTrue(s.startswith('datetime.'))
  581. s = s[9:]
  582. td2 = eval(s)
  583. self.assertEqual(td, td2)
  584. # Verify identity via reconstructing from pieces.
  585. td2 = timedelta(td.days, td.seconds, td.microseconds)
  586. self.assertEqual(td, td2)
  587. def test_resolution_info(self):
  588. self.assertIsInstance(timedelta.min, timedelta)
  589. self.assertIsInstance(timedelta.max, timedelta)
  590. self.assertIsInstance(timedelta.resolution, timedelta)
  591. self.assertTrue(timedelta.max > timedelta.min)
  592. self.assertEqual(timedelta.min, timedelta(-999999999))
  593. self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
  594. self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
  595. def test_overflow(self):
  596. tiny = timedelta.resolution
  597. td = timedelta.min + tiny
  598. td -= tiny # no problem
  599. self.assertRaises(OverflowError, td.__sub__, tiny)
  600. self.assertRaises(OverflowError, td.__add__, -tiny)
  601. td = timedelta.max - tiny
  602. td += tiny # no problem
  603. self.assertRaises(OverflowError, td.__add__, tiny)
  604. self.assertRaises(OverflowError, td.__sub__, -tiny)
  605. self.assertRaises(OverflowError, lambda: -timedelta.max)
  606. day = timedelta(1)
  607. self.assertRaises(OverflowError, day.__mul__, 10**9)
  608. self.assertRaises(OverflowError, day.__mul__, 1e9)
  609. self.assertRaises(OverflowError, day.__truediv__, 1e-20)
  610. self.assertRaises(OverflowError, day.__truediv__, 1e-10)
  611. self.assertRaises(OverflowError, day.__truediv__, 9e-10)
  612. @support.requires_IEEE_754
  613. def _test_overflow_special(self):
  614. day = timedelta(1)
  615. self.assertRaises(OverflowError, day.__mul__, INF)
  616. self.assertRaises(OverflowError, day.__mul__, -INF)
  617. def test_microsecond_rounding(self):
  618. td = timedelta
  619. eq = self.assertEqual
  620. # Single-field rounding.
  621. eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
  622. eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
  623. eq(td(milliseconds=0.5/1000), td(microseconds=0))
  624. eq(td(milliseconds=-0.5/1000), td(microseconds=-0))
  625. eq(td(milliseconds=0.6/1000), td(microseconds=1))
  626. eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
  627. eq(td(milliseconds=1.5/1000), td(microseconds=2))
  628. eq(td(milliseconds=-1.5/1000), td(microseconds=-2))
  629. eq(td(seconds=0.5/10**6), td(microseconds=0))
  630. eq(td(seconds=-0.5/10**6), td(microseconds=-0))
  631. eq(td(seconds=1/2**7), td(microseconds=7812))
  632. eq(td(seconds=-1/2**7), td(microseconds=-7812))
  633. # Rounding due to contributions from more than one field.
  634. us_per_hour = 3600e6
  635. us_per_day = us_per_hour * 24
  636. eq(td(days=.4/us_per_day), td(0))
  637. eq(td(hours=.2/us_per_hour), td(0))
  638. eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
  639. eq(td(days=-.4/us_per_day), td(0))
  640. eq(td(hours=-.2/us_per_hour), td(0))
  641. eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
  642. # Test for a patch in Issue 8860
  643. eq(td(microseconds=0.5), 0.5*td(microseconds=1.0))
  644. eq(td(microseconds=0.5)//td.resolution, 0.5*td.resolution//td.resolution)
  645. def test_massive_normalization(self):
  646. td = timedelta(microseconds=-1)
  647. self.assertEqual((td.days, td.seconds, td.microseconds),
  648. (-1, 24*3600-1, 999999))
  649. def test_bool(self):
  650. self.assertTrue(timedelta(1))
  651. self.assertTrue(timedelta(0, 1))
  652. self.assertTrue(timedelta(0, 0, 1))
  653. self.assertTrue(timedelta(microseconds=1))
  654. self.assertFalse(timedelta(0))
  655. def test_subclass_timedelta(self):
  656. class T(timedelta):
  657. @staticmethod
  658. def from_td(td):
  659. return T(td.days, td.seconds, td.microseconds)
  660. def as_hours(self):
  661. sum = (self.days * 24 +
  662. self.seconds / 3600.0 +
  663. self.microseconds / 3600e6)
  664. return round(sum)
  665. t1 = T(days=1)
  666. self.assertIs(type(t1), T)
  667. self.assertEqual(t1.as_hours(), 24)
  668. t2 = T(days=-1, seconds=-3600)
  669. self.assertIs(type(t2), T)
  670. self.assertEqual(t2.as_hours(), -25)
  671. t3 = t1 + t2
  672. self.assertIs(type(t3), timedelta)
  673. t4 = T.from_td(t3)
  674. self.assertIs(type(t4), T)
  675. self.assertEqual(t3.days, t4.days)
  676. self.assertEqual(t3.seconds, t4.seconds)
  677. self.assertEqual(t3.microseconds, t4.microseconds)
  678. self.assertEqual(str(t3), str(t4))
  679. self.assertEqual(t4.as_hours(), -1)
  680. def test_division(self):
  681. t = timedelta(hours=1, minutes=24, seconds=19)
  682. second = timedelta(seconds=1)
  683. self.assertEqual(t / second, 5059.0)
  684. self.assertEqual(t // second, 5059)
  685. t = timedelta(minutes=2, seconds=30)
  686. minute = timedelta(minutes=1)
  687. self.assertEqual(t / minute, 2.5)
  688. self.assertEqual(t // minute, 2)
  689. zerotd = timedelta(0)
  690. self.assertRaises(ZeroDivisionError, truediv, t, zerotd)
  691. self.assertRaises(ZeroDivisionError, floordiv, t, zerotd)
  692. # self.assertRaises(TypeError, truediv, t, 2)
  693. # note: floor division of a timedelta by an integer *is*
  694. # currently permitted.
  695. def test_remainder(self):
  696. t = timedelta(minutes=2, seconds=30)
  697. minute = timedelta(minutes=1)
  698. r = t % minute
  699. self.assertEqual(r, timedelta(seconds=30))
  700. t = timedelta(minutes=-2, seconds=30)
  701. r = t % minute
  702. self.assertEqual(r, timedelta(seconds=30))
  703. zerotd = timedelta(0)
  704. self.assertRaises(ZeroDivisionError, mod, t, zerotd)
  705. self.assertRaises(TypeError, mod, t, 10)
  706. def test_divmod(self):
  707. t = timedelta(minutes=2, seconds=30)
  708. minute = timedelta(minutes=1)
  709. q, r = divmod(t, minute)
  710. self.assertEqual(q, 2)
  711. self.assertEqual(r, timedelta(seconds=30))
  712. t = timedelta(minutes=-2, seconds=30)
  713. q, r = divmod(t, minute)
  714. self.assertEqual(q, -2)
  715. self.assertEqual(r, timedelta(seconds=30))
  716. zerotd = timedelta(0)
  717. self.assertRaises(ZeroDivisionError, divmod, t, zerotd)
  718. self.assertRaises(TypeError, divmod, t, 10)
  719. #############################################################################
  720. # date tests
  721. class TestDateOnly(unittest.TestCase):
  722. # Tests here won't pass if also run on datetime objects, so don't
  723. # subclass this to test datetimes too.
  724. def test_delta_non_days_ignored(self):
  725. dt = date(2000, 1, 2)
  726. delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
  727. microseconds=5)
  728. days = timedelta(delta.days)
  729. self.assertEqual(days, timedelta(1))
  730. dt2 = dt + delta
  731. self.assertEqual(dt2, dt + days)
  732. dt2 = delta + dt
  733. self.assertEqual(dt2, dt + days)
  734. dt2 = dt - delta
  735. self.assertEqual(dt2, dt - days)
  736. delta = -delta
  737. days = timedelta(delta.days)
  738. self.assertEqual(days, timedelta(-2))
  739. dt2 = dt + delta
  740. self.assertEqual(dt2, dt + days)
  741. dt2 = delta + dt
  742. self.assertEqual(dt2, dt + days)
  743. dt2 = dt - delta
  744. self.assertEqual(dt2, dt - days)
  745. class SubclassDate(date):
  746. sub_var = 1
  747. class TestDate(HarmlessMixedComparison, unittest.TestCase):
  748. # Tests here should pass for both dates and datetimes, except for a
  749. # few tests that TestDateTime overrides.
  750. theclass = date
  751. def test_basic_attributes(self):
  752. dt = self.theclass(2002, 3, 1)
  753. self.assertEqual(dt.year, 2002)
  754. self.assertEqual(dt.month, 3)
  755. self.assertEqual(dt.day, 1)
  756. def test_roundtrip(self):
  757. for dt in (self.theclass(1, 2, 3),
  758. self.theclass.today()):
  759. # Verify dt -> string -> date identity.
  760. s = repr(dt)
  761. self.assertTrue(s.startswith('datetime.'))
  762. s = s[9:]
  763. dt2 = eval(s)
  764. self.assertEqual(dt, dt2)
  765. # Verify identity via reconstructing from pieces.
  766. dt2 = self.theclass(dt.year, dt.month, dt.day)
  767. self.assertEqual(dt, dt2)
  768. def test_ordinal_conversions(self):
  769. # Check some fixed values.
  770. for y, m, d, n in [(1, 1, 1, 1), # calendar origin
  771. (1, 12, 31, 365),
  772. (2, 1, 1, 366),
  773. # first example from "Calendrical Calculations"
  774. (1945, 11, 12, 710347)]:
  775. d = self.theclass(y, m, d)
  776. self.assertEqual(n, d.toordinal())
  777. fromord = self.theclass.fromordinal(n)
  778. self.assertEqual(d, fromord)
  779. if hasattr(fromord, "hour"):
  780. # if we're checking something fancier than a date, verify
  781. # the extra fields have been zeroed out
  782. self.assertEqual(fromord.hour, 0)
  783. self.assertEqual(fromord.minute, 0)
  784. self.assertEqual(fromord.second, 0)
  785. self.assertEqual(fromord.microsecond, 0)
  786. # Check first and last days of year spottily across the whole
  787. # range of years supported.
  788. for year in range(MINYEAR, MAXYEAR+1, 7):
  789. # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
  790. d = self.theclass(year, 1, 1)
  791. n = d.toordinal()
  792. d2 = self.theclass.fromordinal(n)
  793. self.assertEqual(d, d2)
  794. # Verify that moving back a day gets to the end of year-1.
  795. if year > 1:
  796. d = self.theclass.fromordinal(n-1)
  797. d2 = self.theclass(year-1, 12, 31)
  798. self.assertEqual(d, d2)
  799. self.assertEqual(d2.toordinal(), n-1)
  800. # Test every day in a leap-year and a non-leap year.
  801. dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
  802. for year, isleap in (2000, True), (2002, False):
  803. n = self.theclass(year, 1, 1).toordinal()
  804. for month, maxday in zip(range(1, 13), dim):
  805. if month == 2 and isleap:
  806. maxday += 1
  807. for day in range(1, maxday+1):
  808. d = self.theclass(year, month, day)
  809. self.assertEqual(d.toordinal(), n)
  810. self.assertEqual(d, self.theclass.fromordinal(n))
  811. n += 1
  812. def test_extreme_ordinals(self):
  813. a = self.theclass.min
  814. a = self.theclass(a.year, a.month, a.day) # get rid of time parts
  815. aord = a.toordinal()
  816. b = a.fromordinal(aord)
  817. self.assertEqual(a, b)
  818. self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
  819. b = a + timedelta(days=1)
  820. self.assertEqual(b.toordinal(), aord + 1)
  821. self.assertEqual(b, self.theclass.fromordinal(aord + 1))
  822. a = self.theclass.max
  823. a = self.theclass(a.year, a.month, a.day) # get rid of time parts
  824. aord = a.toordinal()
  825. b = a.fromordinal(aord)
  826. self.assertEqual(a, b)
  827. self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
  828. b = a - timedelta(days=1)
  829. self.assertEqual(b.toordinal(), aord - 1)
  830. self.assertEqual(b, self.theclass.fromordinal(aord - 1))
  831. def test_bad_constructor_arguments(self):
  832. # bad years
  833. self.theclass(MINYEAR, 1, 1) # no exception
  834. self.theclass(MAXYEAR, 1, 1) # no exception
  835. self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
  836. self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
  837. # bad months
  838. self.theclass(2000, 1, 1) # no exception
  839. self.theclass(2000, 12, 1) # no exception
  840. self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
  841. self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
  842. # bad days
  843. self.theclass(2000, 2, 29) # no exception
  844. self.theclass(2004, 2, 29) # no exception
  845. self.theclass(2400, 2, 29) # no exception
  846. self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
  847. self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
  848. self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
  849. self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
  850. self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
  851. self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
  852. def test_hash_equality(self):
  853. d = self.theclass(2000, 12, 31)
  854. # same thing
  855. e = self.theclass(2000, 12, 31)
  856. self.assertEqual(d, e)
  857. self.assertEqual(hash(d), hash(e))
  858. dic = {d: 1}
  859. dic[e] = 2
  860. self.assertEqual(len(dic), 1)
  861. self.assertEqual(dic[d], 2)
  862. self.assertEqual(dic[e], 2)
  863. d = self.theclass(2001, 1, 1)
  864. # same thing
  865. e = self.theclass(2001, 1, 1)
  866. self.assertEqual(d, e)
  867. self.assertEqual(hash(d), hash(e))
  868. dic = {d: 1}
  869. dic[e] = 2
  870. self.assertEqual(len(dic), 1)
  871. self.assertEqual(dic[d], 2)
  872. self.assertEqual(dic[e], 2)
  873. def test_computations(self):
  874. a = self.theclass(2002, 1, 31)
  875. b = self.theclass(1956, 1, 31)
  876. c = self.theclass(2001,2,1)
  877. diff = a-b
  878. self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
  879. self.assertEqual(diff.seconds, 0)
  880. self.assertEqual(diff.microseconds, 0)
  881. day = timedelta(1)
  882. week = timedelta(7)
  883. a = self.theclass(2002, 3, 2)
  884. self.assertEqual(a + day, self.theclass(2002, 3, 3))
  885. self.assertEqual(day + a, self.theclass(2002, 3, 3))
  886. self.assertEqual(a - day, self.theclass(2002, 3, 1))
  887. self.assertEqual(-day + a, self.theclass(2002, 3, 1))
  888. self.assertEqual(a + week, self.theclass(2002, 3, 9))
  889. self.assertEqual(a - week, self.theclass(2002, 2, 23))
  890. self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
  891. self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
  892. self.assertEqual((a + week) - a, week)
  893. self.assertEqual((a + day) - a, day)
  894. self.assertEqual((a - week) - a, -week)
  895. self.assertEqual((a - day) - a, -day)
  896. self.assertEqual(a - (a + week), -week)
  897. self.assertEqual(a - (a + day), -day)
  898. self.assertEqual(a - (a - week), week)
  899. self.assertEqual(a - (a - day), day)
  900. self.assertEqual(c - (c - day), day)
  901. # Add/sub ints or floats should be illegal
  902. for i in 1, 1.0:
  903. self.assertRaises(TypeError, lambda: a+i)
  904. self.assertRaises(TypeError, lambda: a-i)
  905. self.assertRaises(TypeError, lambda: i+a)
  906. self.assertRaises(TypeError, lambda: i-a)
  907. # delta - date is senseless.
  908. self.assertRaises(TypeError, lambda: day - a)
  909. # mixing date and (delta or date) via * or // is senseless
  910. self.assertRaises(TypeError, lambda: day * a)
  911. self.assertRaises(TypeError, lambda: a * day)
  912. self.assertRaises(TypeError, lambda: day // a)
  913. self.assertRaises(TypeError, lambda: a // day)
  914. self.assertRaises(TypeError, lambda: a * a)
  915. self.assertRaises(TypeError, lambda: a // a)
  916. # date + date is senseless
  917. self.assertRaises(TypeError, lambda: a + a)
  918. def test_overflow(self):
  919. tiny = self.theclass.resolution
  920. for delta in [tiny, timedelta(1), timedelta(2)]:
  921. dt = self.theclass.min + delta
  922. dt -= delta # no problem
  923. self.assertRaises(OverflowError, dt.__sub__, delta)
  924. self.assertRaises(OverflowError, dt.__add__, -delta)
  925. dt = self.theclass.max - delta
  926. dt += delta # no problem
  927. self.assertRaises(OverflowError, dt.__add__, delta)
  928. self.assertRaises(OverflowError, dt.__sub__, -delta)
  929. def test_fromtimestamp(self):
  930. import time
  931. # Try an arbitrary fixed value.
  932. year, month, day = 1999, 9, 19
  933. ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
  934. d = self.theclass.fromtimestamp(ts)
  935. self.assertEqual(d.year, year)
  936. self.assertEqual(d.month, month)
  937. self.assertEqual(d.day, day)
  938. def test_insane_fromtimestamp(self):
  939. # It's possible that some platform maps time_t to double,
  940. # and that this test will fail there. This test should
  941. # exempt such platforms (provided they return reasonable
  942. # results!).
  943. for insane in -1e200, 1e200:
  944. self.assertRaises(OverflowError, self.theclass.fromtimestamp,
  945. insane)
  946. def test_today(self):
  947. import time
  948. # We claim that today() is like fromtimestamp(time.time()), so
  949. # prove it.
  950. for dummy in range(3):
  951. today = self.theclass.today()
  952. ts = time.time()
  953. todayagain = self.theclass.fromtimestamp(ts)
  954. if today == todayagain:
  955. break
  956. # There are several legit reasons that could fail:
  957. # 1. It recently became midnight, between the today() and the
  958. # time() calls.
  959. # 2. The platform time() has such fine resolution that we'll
  960. # never get the same value twice.
  961. # 3. The platform time() has poor resolution, and we just
  962. # happened to call today() right before a resolution quantum
  963. # boundary.
  964. # 4. The system clock got fiddled between calls.
  965. # In any case, wait a little while and try again.
  966. time.sleep(0.1)
  967. # It worked or it didn't. If it didn't, assume it's reason #2, and
  968. # let the test pass if they're within half a second of each other.
  969. if today != todayagain:
  970. self.assertAlmostEqual(todayagain, today,
  971. delta=timedelta(seconds=0.5))
  972. def test_weekday(self):
  973. for i in range(7):
  974. # March 4, 2002 is a Monday
  975. self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
  976. self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
  977. # January 2, 1956 is a Monday
  978. self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
  979. self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
  980. def test_isocalendar(self):
  981. # Check examples from
  982. # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
  983. for i in range(7):
  984. d = self.theclass(2003, 12, 22+i)
  985. self.assertEqual(d.isocalendar(), (2003, 52, i+1))
  986. d = self.theclass(2003, 12, 29) + timedelta(i)
  987. self.assertEqual(d.isocalendar(), (2004, 1, i+1))
  988. d = self.theclass(2004, 1, 5+i)
  989. self.assertEqual(d.isocalendar(), (2004, 2, i+1))
  990. d = self.theclass(2009, 12, 21+i)
  991. self.assertEqual(d.isocalendar(), (2009, 52, i+1))
  992. d = self.theclass(2009, 12, 28) + timedelta(i)
  993. self.assertEqual(d.isocalendar(), (2009, 53, i+1))
  994. d = self.theclass(2010, 1, 4+i)
  995. self.assertEqual(d.isocalendar(), (2010, 1, i+1))
  996. def test_iso_long_years(self):
  997. # Calculate long ISO years and compare to table from
  998. # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
  999. ISO_LONG_YEARS_TABLE = """
  1000. 4 32 60 88
  1001. 9 37 65 93
  1002. 15 43 71 99
  1003. 20 48 76
  1004. 26 54 82
  1005. 105 133 161 189
  1006. 111 139 167 195
  1007. 116 144 172
  1008. 122 150 178
  1009. 128 156 184
  1010. 201 229 257 285
  1011. 207 235 263 291
  1012. 212 240 268 296
  1013. 218 246 274
  1014. 224 252 280
  1015. 303 331 359 387
  1016. 308 336 364 392
  1017. 314 342 370 398
  1018. 320 348 376
  1019. 325 353 381
  1020. """
  1021. iso_long_years = sorted(map(int, ISO_LONG_YEARS_TABLE.split()))
  1022. L = []
  1023. for i in range(400):
  1024. d = self.theclass(2000+i, 12, 31)
  1025. d1 = self.theclass(1600+i, 12, 31)
  1026. self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
  1027. if d.isocalendar()[1] == 53:
  1028. L.append(i)
  1029. self.assertEqual(L, iso_long_years)
  1030. def test_isoformat(self):
  1031. t = self.theclass(2, 3, 2)
  1032. self.assertEqual(t.isoformat(), "0002-03-02")
  1033. def test_ctime(self):
  1034. t = self.theclass(2002, 3, 2)
  1035. self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
  1036. def test_strftime(self):
  1037. t = self.theclass(2005, 3, 2)
  1038. self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
  1039. self.assertEqual(t.strftime(""), "") # SF bug #761337
  1040. self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784
  1041. self.assertRaises(TypeError, t.strftime) # needs an arg
  1042. self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
  1043. self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
  1044. # test that unicode input is allowed (issue 2782)
  1045. self.assertEqual(t.strftime("%m"), "03")
  1046. # A naive object replaces %z and %Z w/ empty strings.
  1047. self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
  1048. #make sure that invalid format specifiers are handled correctly
  1049. #self.assertRaises(ValueError, t.strftime, "%e")
  1050. #self.assertRaises(ValueError, t.strftime, "%")
  1051. #self.assertRaises(ValueError, t.strftime, "%#")
  1052. #oh well, some systems just ignore those invalid ones.
  1053. #at least, exercise them to make sure that no crashes
  1054. #are generated
  1055. for f in ["%e", "%", "%#"]:
  1056. try:
  1057. t.strftime(f)
  1058. except ValueError:
  1059. pass
  1060. #check that this standard extension works
  1061. t.strftime("%f")
  1062. def test_format(self):
  1063. dt = self.theclass(2007, 9, 10)
  1064. self.assertEqual(dt.__format__(''), str(dt))
  1065. with self.assertRaisesRegex(TypeError, 'must be str, not int'):
  1066. dt.__format__(123)
  1067. # check that a derived class's __str__() gets called
  1068. class A(self.theclass):
  1069. def __str__(self):
  1070. return 'A'
  1071. a = A(2007, 9, 10)
  1072. self.assertEqual(a.__format__(''), 'A')
  1073. # check that a derived class's strftime gets called
  1074. class B(self.theclass):
  1075. def strftime(self, format_spec):
  1076. return 'B'
  1077. b = B(2007, 9, 10)
  1078. self.assertEqual(b.__format__(''), str(dt))
  1079. for fmt in ["m:%m d:%d y:%y",
  1080. "m:%m d:%d y:%y H:%H M:%M S:%S",
  1081. "%z %Z",
  1082. ]:
  1083. self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
  1084. self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
  1085. self.assertEqual(b.__format__(fmt), 'B')
  1086. def test_resolution_info(self):
  1087. # XXX: Should min and max respect subclassing?
  1088. if issubclass(self.theclass, datetime):
  1089. expected_class = datetime
  1090. else:
  1091. expected_class = date
  1092. self.assertIsInstance(self.theclass.min, expected_class)
  1093. self.assertIsInstance(self.theclass.max, expected_class)
  1094. self.assertIsInstance(self.theclass.resolution, timedelta)
  1095. self.assertTrue(self.theclass.max > self.theclass.min)
  1096. def test_extreme_timedelta(self):
  1097. big = self.theclass.max - self.theclass.min
  1098. # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
  1099. n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
  1100. # n == 315537897599999999 ~= 2**58.13
  1101. justasbig = timedelta(0, 0, n)
  1102. self.assertEqual(big, justasbig)
  1103. self.assertEqual(self.theclass.min + big, self.theclass.max)
  1104. self.assertEqual(self.theclass.max - big, self.theclass.min)
  1105. def test_timetuple(self):
  1106. for i in range(7):
  1107. # January 2, 1956 is a Monday (0)
  1108. d = self.theclass(1956, 1, 2+i)
  1109. t = d.timetuple()
  1110. self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
  1111. # February 1, 1956 is a Wednesday (2)
  1112. d = self.theclass(1956, 2, 1+i)
  1113. t = d.timetuple()
  1114. self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
  1115. # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
  1116. # of the year.
  1117. d = self.theclass(1956, 3, 1+i)
  1118. t = d.timetuple()
  1119. self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
  1120. self.assertEqual(t.tm_year, 1956)
  1121. self.assertEqual(t.tm_mon, 3)
  1122. self.assertEqual(t.tm_mday, 1+i)
  1123. self.assertEqual(t.tm_hour, 0)
  1124. self.assertEqual(t.tm_min, 0)
  1125. self.assertEqual(t.tm_sec, 0)
  1126. self.assertEqual(t.tm_wday, (3+i)%7)
  1127. self.assertEqual(t.tm_yday, 61+i)
  1128. self.assertEqual(t.tm_isdst, -1)
  1129. def test_pickling(self):
  1130. args = 6, 7, 23
  1131. orig = self.theclass(*args)
  1132. for pickler, unpickler, proto in pickle_choices:
  1133. green = pickler.dumps(orig, proto)
  1134. derived = unpickler.loads(green)
  1135. self.assertEqual(orig, derived)
  1136. self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
  1137. def test_compare(self):
  1138. t1 = self.theclass(2, 3, 4)
  1139. t2 = self.theclass(2, 3, 4)
  1140. self.assertEqual(t1, t2)
  1141. self.assertTrue(t1 <= t2)
  1142. self.assertTrue(t1 >= t2)
  1143. self.assertFalse(t1 != t2)
  1144. self.assertFalse(t1 < t2)
  1145. self.assertFalse(t1 > t2)
  1146. for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
  1147. t2 = self.theclass(*args) # this is larger than t1
  1148. self.assertTrue(t1 < t2)
  1149. self.assertTrue(t2 > t1)
  1150. self.assertTrue(t1 <= t2)
  1151. self.assertTrue(t2 >= t1)
  1152. self.assertTrue(t1 != t2)
  1153. self.assertTrue(t2 != t1)
  1154. self.assertFalse(t1 == t2)
  1155. self.assertFalse(t2 == t1)
  1156. self.assertFalse(t1 > t2)
  1157. self.assertFalse(t2 < t1)
  1158. self.assertFalse(t1 >= t2)
  1159. self.assertFalse(t2 <= t1)
  1160. for badarg in OTHERSTUFF:
  1161. self.assertEqual(t1 == badarg, False)
  1162. self.assertEqual(t1 != badarg, True)
  1163. self.assertEqual(badarg == t1, False)
  1164. self.assertEqual(badarg != t1, True)
  1165. self.assertRaises(TypeError, lambda: t1 < badarg)
  1166. self.assertRaises(TypeError, lambda: t1 > badarg)
  1167. self.assertRaises(TypeError, lambda: t1 >= badarg)
  1168. self.assertRaises(TypeError, lambda: badarg <= t1)
  1169. self.assertRaises(TypeError, lambda: badarg < t1)
  1170. self.assertRaises(TypeError, lambda: badarg > t1)
  1171. self.assertRaises(TypeError, lambda: badarg >= t1)
  1172. def test_mixed_compare(self):
  1173. our = self.theclass(2000, 4, 5)
  1174. # Our class can be compared for equality to other classes
  1175. self.assertEqual(our == 1, False)
  1176. self.assertEqual(1 == our, False)
  1177. self.assertEqual(our != 1, True)
  1178. self.assertEqual(1 != our, True)
  1179. # But the ordering is undefined
  1180. self.assertRaises(TypeError, lambda: our < 1)
  1181. self.assertRaises(TypeError, lambda: 1 < our)
  1182. # Repeat those tests with a different class
  1183. class SomeClass:
  1184. pass
  1185. their = SomeClass()
  1186. self.assertEqual(our == their, False)
  1187. self.assertEqual(their == our, False)
  1188. self.assertEqual(our != their, True)
  1189. self.assertEqual(their != our, True)
  1190. self.assertRaises(TypeError, lambda: our < their)
  1191. self.assertRaises(TypeError, lambda: their < our)
  1192. # However, if the other class explicitly defines ordering
  1193. # relative to our class, it is allowed to do so
  1194. class LargerThanAnything:
  1195. def __lt__(self, other):
  1196. return False
  1197. def __le__(self, other):
  1198. return isinstance(other, LargerThanAnything)
  1199. def __eq__(self, other):
  1200. return isinstance(other, LargerThanAnything)
  1201. def __gt__(self, other):
  1202. return not isinstance(other, LargerThanAnything)
  1203. def __ge__(self, other):
  1204. return True
  1205. their = LargerThanAnything()
  1206. self.assertEqual(our == their, False)
  1207. self.assertEqual(their == our, False)
  1208. self.assertEqual(our != their, True)
  1209. self.assertEqual(their != our, True)
  1210. self.assertEqual(our < their, True)
  1211. self.assertEqual(their < our, False)
  1212. def test_bool(self):
  1213. # All dates are considered true.
  1214. self.assertTrue(self.theclass.min)
  1215. self.assertTrue(self.theclass.max)
  1216. def test_strftime_y2k(self):
  1217. for y in (1, 49, 70, 99, 100, 999, 1000, 1970):
  1218. d = self.theclass(y, 1, 1)
  1219. # Issue 13305: For years < 1000, the value is not always
  1220. # padded to 4 digits across platforms. The C standard
  1221. # assumes year >= 1900, so it does not specify the number
  1222. # of digits.
  1223. if d.strftime("%Y") != '%04d' % y:
  1224. # Year 42 returns '42', not padded
  1225. self.assertEqual(d.strftime("%Y"), '%d' % y)
  1226. # '0042' is obtained anyway
  1227. self.assertEqual(d.strftime("%4Y"), '%04d' % y)
  1228. def test_replace(self):
  1229. cls = self.theclass
  1230. args = [1, 2, 3]
  1231. base = cls(*args)
  1232. self.assertEqual(base, base.replace())
  1233. i = 0
  1234. for name, newval in (("year", 2),
  1235. ("month", 3),
  1236. ("day", 4)):
  1237. newargs = args[:]
  1238. newargs[i] = newval
  1239. expected = cls(*newargs)
  1240. got = base.replace(**{name: newval})
  1241. self.assertEqual(expected, got)
  1242. i += 1
  1243. # Out of bounds.
  1244. base = cls(2000, 2, 29)
  1245. self.assertRaises(ValueError, base.replace, year=2001)
  1246. def test_subclass_date(self):
  1247. class C(self.theclass):
  1248. theAnswer = 42
  1249. def __new__(cls, *args, **kws):
  1250. temp = kws.copy()
  1251. extra = temp.pop('extra')
  1252. result = self.theclass.__new__(cls, *args, **temp)
  1253. result.extra = extra
  1254. return result
  1255. def newmeth(self, start):
  1256. return start + self.year + self.month
  1257. args = 2003, 4, 14
  1258. dt1 = self.theclass(*args)
  1259. dt2 = C(*args, **{'extra': 7})
  1260. self.assertEqual(dt2.__class__, C)
  1261. self.assertEqual(dt2.theAnswer, 42)
  1262. self.assertEqual(dt2.extra, 7)
  1263. self.assertEqual(dt1.toordinal(), dt2.toordinal())
  1264. self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
  1265. def test_pickling_subclass_date(self):
  1266. args = 6, 7, 23
  1267. orig = SubclassDate(*args)
  1268. for pickler, unpickler, proto in pickle_choices:
  1269. green = pickler.dumps(orig, proto)
  1270. derived = unpickler.loads(green)
  1271. self.assertEqual(orig, derived)
  1272. def test_backdoor_resistance(self):
  1273. # For fast unpickling, the constructor accepts a pickle byte string.
  1274. # This is a low-overhead backdoor. A user can (by intent or
  1275. # mistake) pass a string directly, which (if it's the right length)
  1276. # will get treated like a pickle, and bypass the normal sanity
  1277. # checks in the constructor. This can create insane objects.
  1278. # The constructor doesn't want to burn the time to validate all
  1279. # fields, but does check the month field. This stops, e.g.,
  1280. # datetime.datetime('1995-03-25') from yielding an insane object.
  1281. base = b'1995-03-25'
  1282. if not issubclass(self.theclass, datetime):
  1283. base = base[:4]
  1284. for month_byte in b'9', b'\0', b'\r', b'\xff':
  1285. self.assertRaises(TypeError, self.theclass,
  1286. base[:2] + month_byte + base[3:])
  1287. if issubclass(self.theclass, datetime):
  1288. # Good bytes, but bad tzinfo:
  1289. with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
  1290. self.theclass(bytes([1] * len(base)), 'EST')
  1291. for ord_byte in range(1, 13):
  1292. # This shouldn't blow up because of the month byte alone. If
  1293. # the implementation changes to do more-careful checking, it may
  1294. # blow up because other fields are insane.
  1295. self.theclass(base[:2] + bytes([ord_byte]) + base[3:])
  1296. #############################################################################
  1297. # datetime tests
  1298. class SubclassDatetime(datetime):
  1299. sub_var = 1
  1300. class TestDateTime(TestDate):
  1301. theclass = datetime
  1302. def test_basic_attributes(self):
  1303. dt = self.theclass(2002, 3, 1, 12, 0)
  1304. self.assertEqual(dt.year, 2002)
  1305. self.assertEqual(dt.month, 3)
  1306. self.assertEqual(dt.day, 1)
  1307. self.assertEqual(dt.hour, 12)
  1308. self.assertEqual(dt.minute, 0)
  1309. self.assertEqual(dt.second, 0)
  1310. self.assertEqual(dt.microsecond, 0)
  1311. def test_basic_attributes_nonzero(self):
  1312. # Make sure all attributes are non-zero so bugs in
  1313. # bit-shifting access show up.
  1314. dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
  1315. self.assertEqual(dt.year, 2002)
  1316. self.assertEqual(dt.month, 3)
  1317. self.assertEqual(dt.day, 1)
  1318. self.assertEqual(dt.hour, 12)
  1319. self.assertEqual(dt.minute, 59)
  1320. self.assertEqual(dt.second, 59)
  1321. self.assertEqual(dt.microsecond, 8000)
  1322. def test_roundtrip(self):
  1323. for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
  1324. self.theclass.now()):
  1325. # Verify dt -> string -> datetime identity.
  1326. s = repr(dt)
  1327. self.assertTrue(s.startswith('datetime.'))
  1328. s = s[9:]
  1329. dt2 = eval(s)
  1330. self.assertEqual(dt, dt2)
  1331. # Verify identity via reconstructing from pieces.
  1332. dt2 = self.theclass(dt.year, dt.month, dt.day,
  1333. dt.hour, dt.minute, dt.second,
  1334. dt.microsecond)
  1335. self.assertEqual(dt, dt2)
  1336. def test_isoformat(self):
  1337. t = self.theclass(1, 2, 3, 4, 5, 1, 123)
  1338. self.assertEqual(t.isoformat(), "0001-02-03T04:05:01.000123")
  1339. self.assertEqual(t.isoformat('T'), "0001-02-03T04:05:01.000123")
  1340. self.assertEqual(t.isoformat(' '), "0001-02-03 04:05:01.000123")
  1341. self.assertEqual(t.isoformat('\x00'), "0001-02-03\x0004:05:01.000123")
  1342. self.assertEqual(t.isoformat(timespec='hours'), "0001-02-03T04")
  1343. self.assertEqual(t.isoformat(timespec='minutes'), "0001-02-03T04:05")
  1344. self.assertEqual(t.isoformat(timespec='seconds'), "0001-02-03T04:05:01")
  1345. self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
  1346. self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000123")
  1347. self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01.000123")
  1348. self.assertEqual(t.isoformat(sep=' ', timespec='minutes'), "0001-02-03 04:05")
  1349. self.assertRaises(ValueError, t.isoformat, timespec='foo')
  1350. # str is ISO format with the separator forced to a blank.
  1351. self.assertEqual(str(t), "0001-02-03 04:05:01.000123")
  1352. t = self.theclass(1, 2, 3, 4, 5, 1, 999500, tzinfo=timezone.utc)
  1353. self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999+00:00")
  1354. t = self.theclass(1, 2, 3, 4, 5, 1, 999500)
  1355. self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999")
  1356. t = self.theclass(1, 2, 3, 4, 5, 1)
  1357. self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01")
  1358. self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
  1359. self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000000")
  1360. t = self.theclass(2, 3, 2)
  1361. self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
  1362. self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
  1363. self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
  1364. # str is ISO format with the separator forced to a blank.
  1365. self.assertEqual(str(t), "0002-03-02 00:00:00")
  1366. # ISO format with timezone
  1367. tz = FixedOffset(timedelta(seconds=16), 'XXX')
  1368. t = self.theclass(2, 3, 2, tzinfo=tz)
  1369. self.assertEqual(t.isoformat(), "0002-03-02T00:00:00+00:00:16")
  1370. def test_format(self):
  1371. dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
  1372. self.assertEqual(dt.__format__(''), str(dt))
  1373. with self.assertRaisesRegex(TypeError, 'must be str, not int'):
  1374. dt.__format__(123)
  1375. # check that a derived class's __str__() gets called
  1376. class A(self.theclass):
  1377. def __str__(self):
  1378. return 'A'
  1379. a = A(2007, 9, 10, 4, 5, 1, 123)
  1380. self.assertEqual(a.__format__(''), 'A')
  1381. # check that a derived class's strftime gets called
  1382. class B(self.theclass):
  1383. def strftime(self, format_spec):
  1384. return 'B'
  1385. b = B(2007, 9, 10, 4, 5, 1, 123)
  1386. self.assertEqual(b.__format__(''), str(dt))
  1387. for fmt in ["m:%m d:%d y:%y",
  1388. "m:%m d:%d y:%y H:%H M:%M S:%S",
  1389. "%z %Z",
  1390. ]:
  1391. self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
  1392. self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
  1393. self.assertEqual(b.__format__(fmt), 'B')
  1394. def test_more_ctime(self):
  1395. # Test fields that TestDate doesn't touch.
  1396. import time
  1397. t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
  1398. self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
  1399. # Oops! The next line fails on Win2K under MSVC 6, so it's commented
  1400. # out. The difference is that t.ctime() produces " 2" for the day,
  1401. # but platform ctime() produces "02" for the day. According to
  1402. # C99, t.ctime() is correct here.
  1403. # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
  1404. # So test a case where that difference doesn't matter.
  1405. t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
  1406. self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
  1407. def test_tz_independent_comparing(self):
  1408. dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
  1409. dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
  1410. dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
  1411. self.assertEqual(dt1, dt3)
  1412. self.assertTrue(dt2 > dt3)
  1413. # Make sure comparison doesn't forget microseconds, and isn't done
  1414. # via comparing a float timestamp (an IEEE double doesn't have enough
  1415. # precision to span microsecond resolution across years 1 thru 9999,
  1416. # so comparing via timestamp necessarily calls some distinct values
  1417. # equal).
  1418. dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
  1419. us = timedelta(microseconds=1)
  1420. dt2 = dt1 + us
  1421. self.assertEqual(dt2 - dt1, us)
  1422. self.assertTrue(dt1 < dt2)
  1423. def test_strftime_with_bad_tzname_replace(self):
  1424. # verify ok if tzinfo.tzname().replace() returns a non-string
  1425. class MyTzInfo(FixedOffset):
  1426. def tzname(self, dt):
  1427. class MyStr(str):
  1428. def replace(self, *args):
  1429. return None
  1430. return MyStr('name')
  1431. t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
  1432. self.assertRaises(TypeError, t.strftime, '%Z')
  1433. def test_bad_constructor_arguments(self):
  1434. # bad years
  1435. self.theclass(MINYEAR, 1, 1) # no exception
  1436. self.theclass(MAXYEAR, 1, 1) # no exception
  1437. self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
  1438. self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
  1439. # bad months
  1440. self.theclass(2000, 1, 1) # no exception
  1441. self.theclass(2000, 12, 1) # no exception
  1442. self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
  1443. self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
  1444. # bad days
  1445. self.theclass(2000, 2, 29) # no exception
  1446. self.theclass(2004, 2, 29) # no exception
  1447. self.theclass(2400, 2, 29) # no exception
  1448. self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
  1449. self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
  1450. self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
  1451. self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
  1452. self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
  1453. self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
  1454. # bad hours
  1455. self.theclass(2000, 1, 31, 0) # no exception
  1456. self.theclass(2000, 1, 31, 23) # no exception
  1457. self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
  1458. self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
  1459. # bad minutes
  1460. self.theclass(2000, 1, 31, 23, 0) # no exception
  1461. self.theclass(2000, 1, 31, 23, 59) # no exception
  1462. self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
  1463. self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
  1464. # bad seconds
  1465. self.theclass(2000, 1, 31, 23, 59, 0) # no exception
  1466. self.theclass(2000, 1, 31, 23, 59, 59) # no exception
  1467. self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
  1468. self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
  1469. # bad microseconds
  1470. self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
  1471. self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
  1472. self.assertRaises(ValueError, self.theclass,
  1473. 2000, 1, 31, 23, 59, 59, -1)
  1474. self.assertRaises(ValueError, self.theclass,
  1475. 2000, 1, 31, 23, 59, 59,
  1476. 1000000)
  1477. # bad fold
  1478. self.assertRaises(ValueError, self.theclass,
  1479. 2000, 1, 31, fold=-1)
  1480. self.assertRaises(ValueError, self.theclass,
  1481. 2000, 1, 31, fold=2)
  1482. # Positional fold:
  1483. self.assertRaises(TypeError, self.theclass,
  1484. 2000, 1, 31, 23, 59, 59, 0, None, 1)
  1485. def test_hash_equality(self):
  1486. d = self.theclass(2000, 12, 31, 23, 30, 17)
  1487. e = self.theclass(2000, 12, 31, 23, 30, 17)
  1488. self.assertEqual(d, e)
  1489. self.assertEqual(hash(d), hash(e))
  1490. dic = {d: 1}
  1491. dic[e] = 2
  1492. self.assertEqual(len(dic), 1)
  1493. self.assertEqual(dic[d], 2)
  1494. self.assertEqual(dic[e], 2)
  1495. d = self.theclass(2001, 1, 1, 0, 5, 17)
  1496. e = self.theclass(2001, 1, 1, 0, 5, 17)
  1497. self.assertEqual(d, e)
  1498. self.assertEqual(hash(d), hash(e))
  1499. dic = {d: 1}
  1500. dic[e] = 2
  1501. self.assertEqual(len(dic), 1)
  1502. self.assertEqual(dic[d], 2)
  1503. self.assertEqual(dic[e], 2)
  1504. def test_computations(self):
  1505. a = self.theclass(2002, 1, 31)
  1506. b = self.theclass(1956, 1, 31)
  1507. diff = a-b
  1508. self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
  1509. self.assertEqual(diff.seconds, 0)
  1510. self.assertEqual(diff.microseconds, 0)
  1511. a = self.theclass(2002, 3, 2, 17, 6)
  1512. millisec = timedelta(0, 0, 1000)
  1513. hour = timedelta(0, 3600)
  1514. day = timedelta(1)
  1515. week = timedelta(7)
  1516. self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
  1517. self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
  1518. self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
  1519. self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
  1520. self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
  1521. self.assertEqual(a - hour, a + -hour)
  1522. self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
  1523. self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
  1524. self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
  1525. self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
  1526. self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
  1527. self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
  1528. self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
  1529. self.assertEqual((a + week) - a, week)
  1530. self.assertEqual((a + day) - a, day)
  1531. self.assertEqual((a + hour) - a, hour)
  1532. self.assertEqual((a + millisec) - a, millisec)
  1533. self.assertEqual((a - week) - a, -week)
  1534. self.assertEqual((a - day) - a, -day)
  1535. self.assertEqual((a - hour) - a, -hour)
  1536. self.assertEqual((a - millisec) - a, -millisec)
  1537. self.assertEqual(a - (a + week), -week)
  1538. self.assertEqual(a - (a + day), -day)
  1539. self.assertEqual(a - (a + hour), -hour)
  1540. self.assertEqual(a - (a + millisec), -millisec)
  1541. self.assertEqual(a - (a - week), week)
  1542. self.assertEqual(a - (a - day), day)
  1543. self.assertEqual(a - (a - hour), hour)
  1544. self.assertEqual(a - (a - millisec), millisec)
  1545. self.assertEqual(a + (week + day + hour + millisec),
  1546. self.theclass(2002, 3, 10, 18, 6, 0, 1000))
  1547. self.assertEqual(a + (week + day + hour + millisec),
  1548. (((a + week) + day) + hour) + millisec)
  1549. self.assertEqual(a - (week + day + hour + millisec),
  1550. self.theclass(2002, 2, 22, 16, 5, 59, 999000))
  1551. self.assertEqual(a - (week + day + hour + millisec),
  1552. (((a - week) - day) - hour) - millisec)
  1553. # Add/sub ints or floats should be illegal
  1554. for i in 1, 1.0:
  1555. self.assertRaises(TypeError, lambda: a+i)
  1556. self.assertRaises(TypeError, lambda: a-i)
  1557. self.assertRaises(TypeError, lambda: i+a)
  1558. self.assertRaises(TypeError, lambda: i-a)
  1559. # delta - datetime is senseless.
  1560. self.assertRaises(TypeError, lambda: day - a)
  1561. # mixing datetime and (delta or datetime) via * or // is senseless
  1562. self.assertRaises(TypeError, lambda: day * a)
  1563. self.assertRaises(TypeError, lambda: a * day)
  1564. self.assertRaises(TypeError, lambda: day // a)
  1565. self.assertRaises(TypeError, lambda: a // day)
  1566. self.assertRaises(TypeError, lambda: a * a)
  1567. self.assertRaises(TypeError, lambda: a // a)
  1568. # datetime + datetime is senseless
  1569. self.assertRaises(TypeError, lambda: a + a)
  1570. def test_pickling(self):
  1571. args = 6, 7, 23, 20, 59, 1, 64**2
  1572. orig = self.theclass(*args)
  1573. for pickler, unpickler, proto in pickle_choices:
  1574. green = pickler.dumps(orig, proto)
  1575. derived = unpickler.loads(green)
  1576. self.assertEqual(orig, derived)
  1577. self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
  1578. def test_more_pickling(self):
  1579. a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
  1580. for proto in range(pickle.HIGHEST_PROTOCOL + 1):
  1581. s = pickle.dumps(a, proto)
  1582. b = pickle.loads(s)
  1583. self.assertEqual(b.year, 2003)
  1584. self.assertEqual(b.month, 2)
  1585. self.assertEqual(b.day, 7)
  1586. def test_pickling_subclass_datetime(self):
  1587. args = 6, 7, 23, 20, 59, 1, 64**2
  1588. orig = SubclassDatetime(*args)
  1589. for pickler, unpickler, proto in pickle_choices:
  1590. green = pickler.dumps(orig, proto)
  1591. derived = unpickler.loads(green)
  1592. self.assertEqual(orig, derived)
  1593. def test_more_compare(self):
  1594. # The test_compare() inherited from TestDate covers the error cases.
  1595. # We just want to test lexicographic ordering on the members datetime
  1596. # has that date lacks.
  1597. args = [2000, 11, 29, 20, 58, 16, 999998]
  1598. t1 = self.theclass(*args)
  1599. t2 = self.theclass(*args)
  1600. self.assertEqual(t1, t2)
  1601. self.assertTrue(t1 <= t2)
  1602. self.assertTrue(t1 >= t2)
  1603. self.assertFalse(t1 != t2)
  1604. self.assertFalse(t1 < t2)
  1605. self.assertFalse(t1 > t2)
  1606. for i in range(len(args)):
  1607. newargs = args[:]
  1608. newargs[i] = args[i] + 1
  1609. t2 = self.theclass(*newargs) # this is larger than t1
  1610. self.assertTrue(t1 < t2)
  1611. self.assertTrue(t2 > t1)
  1612. self.assertTrue(t1 <= t2)
  1613. self.assertTrue(t2 >= t1)
  1614. self.assertTrue(t1 != t2)
  1615. self.assertTrue(t2 != t1)
  1616. self.assertFalse(t1 == t2)
  1617. self.assertFalse(t2 == t1)
  1618. self.assertFalse(t1 > t2)
  1619. self.assertFalse(t2 < t1)
  1620. self.assertFalse(t1 >= t2)
  1621. self.assertFalse(t2 <= t1)
  1622. # A helper for timestamp constructor tests.
  1623. def verify_field_equality(self, expected, got):
  1624. self.assertEqual(expected.tm_year, got.year)
  1625. self.assertEqual(expected.tm_mon, got.month)
  1626. self.assertEqual(expected.tm_mday, got.day)
  1627. self.assertEqual(expected.tm_hour, got.hour)
  1628. self.assertEqual(expected.tm_min, got.minute)
  1629. self.assertEqual(expected.tm_sec, got.second)
  1630. def test_fromtimestamp(self):
  1631. import time
  1632. ts = time.time()
  1633. expected = time.localtime(ts)
  1634. got = self.theclass.fromtimestamp(ts)
  1635. self.verify_field_equality(expected, got)
  1636. def test_utcfromtimestamp(self):
  1637. import time
  1638. ts = time.time()
  1639. expected = time.gmtime(ts)
  1640. got = self.theclass.utcfromtimestamp(ts)
  1641. self.verify_field_equality(expected, got)
  1642. # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
  1643. # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
  1644. @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
  1645. def test_timestamp_naive(self):
  1646. t = self.theclass(1970, 1, 1)
  1647. self.assertEqual(t.timestamp(), 18000.0)
  1648. t = self.theclass(1970, 1, 1, 1, 2, 3, 4)
  1649. self.assertEqual(t.timestamp(),
  1650. 18000.0 + 3600 + 2*60 + 3 + 4*1e-6)
  1651. # Missing hour
  1652. t0 = self.theclass(2012, 3, 11, 2, 30)
  1653. t1 = t0.replace(fold=1)
  1654. self.assertEqual(self.theclass.fromtimestamp(t1.timestamp()),
  1655. t0 - timedelta(hours=1))
  1656. self.assertEqual(self.theclass.fromtimestamp(t0.timestamp()),
  1657. t1 + timedelta(hours=1))
  1658. # Ambiguous hour defaults to DST
  1659. t = self.theclass(2012, 11, 4, 1, 30)
  1660. self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
  1661. # Timestamp may raise an overflow error on some platforms
  1662. # XXX: Do we care to support the first and last year?
  1663. for t in [self.theclass(2,1,1), self.theclass(9998,12,12)]:
  1664. try:
  1665. s = t.timestamp()
  1666. except OverflowError:
  1667. pass
  1668. else:
  1669. self.assertEqual(self.theclass.fromtimestamp(s), t)
  1670. def test_timestamp_aware(self):
  1671. t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
  1672. self.assertEqual(t.timestamp(), 0.0)
  1673. t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
  1674. self.assertEqual(t.timestamp(),
  1675. 3600 + 2*60 + 3 + 4*1e-6)
  1676. t = self.theclass(1970, 1, 1, 1, 2, 3, 4,
  1677. tzinfo=timezone(timedelta(hours=-5), 'EST'))
  1678. self.assertEqual(t.timestamp(),
  1679. 18000 + 3600 + 2*60 + 3 + 4*1e-6)
  1680. @support.run_with_tz('MSK-03') # Something east of Greenwich
  1681. def test_microsecond_rounding(self):
  1682. for fts in [self.theclass.fromtimestamp,
  1683. self.theclass.utcfromtimestamp]:
  1684. zero = fts(0)
  1685. self.assertEqual(zero.second, 0)
  1686. self.assertEqual(zero.microsecond, 0)
  1687. one = fts(1e-6)
  1688. try:
  1689. minus_one = fts(-1e-6)
  1690. except OSError:
  1691. # localtime(-1) and gmtime(-1) is not supported on Windows
  1692. pass
  1693. else:
  1694. self.assertEqual(minus_one.second, 59)
  1695. self.assertEqual(minus_one.microsecond, 999999)
  1696. t = fts(-1e-8)
  1697. self.assertEqual(t, zero)
  1698. t = fts(-9e-7)
  1699. self.assertEqual(t, minus_one)
  1700. t = fts(-1e-7)
  1701. self.assertEqual(t, zero)
  1702. t = fts(-1/2**7)
  1703. self.assertEqual(t.second, 59)
  1704. self.assertEqual(t.microsecond, 992188)
  1705. t = fts(1e-7)
  1706. self.assertEqual(t, zero)
  1707. t = fts(9e-7)
  1708. self.assertEqual(t, one)
  1709. t = fts(0.99999949)
  1710. self.assertEqual(t.second, 0)
  1711. self.assertEqual(t.microsecond, 999999)
  1712. t = fts(0.9999999)
  1713. self.assertEqual(t.second, 1)
  1714. self.assertEqual(t.microsecond, 0)
  1715. t = fts(1/2**7)
  1716. self.assertEqual(t.second, 0)
  1717. self.assertEqual(t.microsecond, 7812)
  1718. def test_timestamp_limits(self):
  1719. # minimum timestamp
  1720. min_dt = self.theclass.min.replace(tzinfo=timezone.utc)
  1721. min_ts = min_dt.timestamp()
  1722. try:
  1723. # date 0001-01-01 00:00:00+00:00: timestamp=-62135596800
  1724. self.assertEqual(self.theclass.fromtimestamp(min_ts, tz=timezone.utc),
  1725. min_dt)
  1726. except (OverflowError, OSError) as exc:
  1727. # the date 0001-01-01 doesn't fit into 32-bit time_t,
  1728. # or platform doesn't support such very old date
  1729. self.skipTest(str(exc))
  1730. # maximum timestamp: set seconds to zero to avoid rounding issues
  1731. max_dt = self.theclass.max.replace(tzinfo=timezone.utc,
  1732. second=0, microsecond=0)
  1733. max_ts = max_dt.timestamp()
  1734. # date 9999-12-31 23:59:00+00:00: timestamp 253402300740
  1735. self.assertEqual(self.theclass.fromtimestamp(max_ts, tz=timezone.utc),
  1736. max_dt)
  1737. # number of seconds greater than 1 year: make sure that the new date
  1738. # is not valid in datetime.datetime limits
  1739. delta = 3600 * 24 * 400
  1740. # too small
  1741. ts = min_ts - delta
  1742. # converting a Python int to C time_t can raise a OverflowError,
  1743. # especially on 32-bit platforms.
  1744. with self.assertRaises((ValueError, OverflowError)):
  1745. self.theclass.fromtimestamp(ts)
  1746. with self.assertRaises((ValueError, OverflowError)):
  1747. self.theclass.utcfromtimestamp(ts)
  1748. # too big
  1749. ts = max_dt.timestamp() + delta
  1750. with self.assertRaises((ValueError, OverflowError)):
  1751. self.theclass.fromtimestamp(ts)
  1752. with self.assertRaises((ValueError, OverflowError)):
  1753. self.theclass.utcfromtimestamp(ts)
  1754. def test_insane_fromtimestamp(self):
  1755. # It's possible that some platform maps time_t to double,
  1756. # and that this test will fail there. This test should
  1757. # exempt such platforms (provided they return reasonable
  1758. # results!).
  1759. for insane in -1e200, 1e200:
  1760. self.assertRaises(OverflowError, self.theclass.fromtimestamp,
  1761. insane)
  1762. def test_insane_utcfromtimestamp(self):
  1763. # It's possible that some platform maps time_t to double,
  1764. # and that this test will fail there. This test should
  1765. # exempt such platforms (provided they return reasonable
  1766. # results!).
  1767. for insane in -1e200, 1e200:
  1768. self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
  1769. insane)
  1770. @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
  1771. def test_negative_float_fromtimestamp(self):
  1772. # The result is tz-dependent; at least test that this doesn't
  1773. # fail (like it did before bug 1646728 was fixed).
  1774. self.theclass.fromtimestamp(-1.05)
  1775. @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
  1776. def test_negative_float_utcfromtimestamp(self):
  1777. d = self.theclass.utcfromtimestamp(-1.05)
  1778. self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
  1779. def test_utcnow(self):
  1780. import time
  1781. # Call it a success if utcnow() and utcfromtimestamp() are within
  1782. # a second of each other.
  1783. tolerance = timedelta(seconds=1)
  1784. for dummy in range(3):
  1785. from_now = self.theclass.utcnow()
  1786. from_timestamp = self.theclass.utcfromtimestamp(time.time())
  1787. if abs(from_timestamp - from_now) <= tolerance:
  1788. break
  1789. # Else try again a few times.
  1790. self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
  1791. def test_strptime(self):
  1792. string = '2004-12-01 13:02:47.197'
  1793. format = '%Y-%m-%d %H:%M:%S.%f'
  1794. expected = _strptime._strptime_datetime(self.theclass, string, format)
  1795. got = self.theclass.strptime(string, format)
  1796. self.assertEqual(expected, got)
  1797. self.assertIs(type(expected), self.theclass)
  1798. self.assertIs(type(got), self.theclass)
  1799. strptime = self.theclass.strptime
  1800. self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
  1801. self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
  1802. # Only local timezone and UTC are supported
  1803. for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
  1804. (-_time.timezone, _time.tzname[0])):
  1805. if tzseconds < 0:
  1806. sign = '-'
  1807. seconds = -tzseconds
  1808. else:
  1809. sign ='+'
  1810. seconds = tzseconds
  1811. hours, minutes = divmod(seconds//60, 60)
  1812. dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
  1813. dt = strptime(dtstr, "%z %Z")
  1814. self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
  1815. self.assertEqual(dt.tzname(), tzname)
  1816. # Can produce inconsistent datetime
  1817. dtstr, fmt = "+1234 UTC", "%z %Z"
  1818. dt = strptime(dtstr, fmt)
  1819. self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
  1820. self.assertEqual(dt.tzname(), 'UTC')
  1821. # yet will roundtrip
  1822. self.assertEqual(dt.strftime(fmt), dtstr)
  1823. # Produce naive datetime if no %z is provided
  1824. self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
  1825. with self.assertRaises(ValueError): strptime("-2400", "%z")
  1826. with self.assertRaises(ValueError): strptime("-000", "%z")
  1827. def test_more_timetuple(self):
  1828. # This tests fields beyond those tested by the TestDate.test_timetuple.
  1829. t = self.theclass(2004, 12, 31, 6, 22, 33)
  1830. self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
  1831. self.assertEqual(t.timetuple(),
  1832. (t.year, t.month, t.day,
  1833. t.hour, t.minute, t.second,
  1834. t.weekday(),
  1835. t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
  1836. -1))
  1837. tt = t.timetuple()
  1838. self.assertEqual(tt.tm_year, t.year)
  1839. self.assertEqual(tt.tm_mon, t.month)
  1840. self.assertEqual(tt.tm_mday, t.day)
  1841. self.assertEqual(tt.tm_hour, t.hour)
  1842. self.assertEqual(tt.tm_min, t.minute)
  1843. self.assertEqual(tt.tm_sec, t.second)
  1844. self.assertEqual(tt.tm_wday, t.weekday())
  1845. self.assertEqual(tt.tm_yday, t.toordinal() -
  1846. date(t.year, 1, 1).toordinal() + 1)
  1847. self.assertEqual(tt.tm_isdst, -1)
  1848. def test_more_strftime(self):
  1849. # This tests fields beyond those tested by the TestDate.test_strftime.
  1850. t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
  1851. self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
  1852. "12 31 04 000047 33 22 06 366")
  1853. tz = timezone(-timedelta(hours=2, seconds=33, microseconds=123))
  1854. t = t.replace(tzinfo=tz)
  1855. self.assertEqual(t.strftime("%z"), "-020033.000123")
  1856. def test_extract(self):
  1857. dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
  1858. self.assertEqual(dt.date(), date(2002, 3, 4))
  1859. self.assertEqual(dt.time(), time(18, 45, 3, 1234))
  1860. def test_combine(self):
  1861. d = date(2002, 3, 4)
  1862. t = time(18, 45, 3, 1234)
  1863. expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
  1864. combine = self.theclass.combine
  1865. dt = combine(d, t)
  1866. self.assertEqual(dt, expected)
  1867. dt = combine(time=t, date=d)
  1868. self.assertEqual(dt, expected)
  1869. self.assertEqual(d, dt.date())
  1870. self.assertEqual(t, dt.time())
  1871. self.assertEqual(dt, combine(dt.date(), dt.time()))
  1872. self.assertRaises(TypeError, combine) # need an arg
  1873. self.assertRaises(TypeError, combine, d) # need two args
  1874. self.assertRaises(TypeError, combine, t, d) # args reversed
  1875. self.assertRaises(TypeError, combine, d, t, 1) # wrong tzinfo type
  1876. self.assertRaises(TypeError, combine, d, t, 1, 2) # too many args
  1877. self.assertRaises(TypeError, combine, "date", "time") # wrong types
  1878. self.assertRaises(TypeError, combine, d, "time") # wrong type
  1879. self.assertRaises(TypeError, combine, "date", t) # wrong type
  1880. # tzinfo= argument
  1881. dt = combine(d, t, timezone.utc)
  1882. self.assertIs(dt.tzinfo, timezone.utc)
  1883. dt = combine(d, t, tzinfo=timezone.utc)
  1884. self.assertIs(dt.tzinfo, timezone.utc)
  1885. t = time()
  1886. dt = combine(dt, t)
  1887. self.assertEqual(dt.date(), d)
  1888. self.assertEqual(dt.time(), t)
  1889. def test_replace(self):
  1890. cls = self.theclass
  1891. args = [1, 2, 3, 4, 5, 6, 7]
  1892. base = cls(*args)
  1893. self.assertEqual(base, base.replace())
  1894. i = 0
  1895. for name, newval in (("year", 2),
  1896. ("month", 3),
  1897. ("day", 4),
  1898. ("hour", 5),
  1899. ("minute", 6),
  1900. ("second", 7),
  1901. ("microsecond", 8)):
  1902. newargs = args[:]
  1903. newargs[i] = newval
  1904. expected = cls(*newargs)
  1905. got = base.replace(**{name: newval})
  1906. self.assertEqual(expected, got)
  1907. i += 1
  1908. # Out of bounds.
  1909. base = cls(2000, 2, 29)
  1910. self.assertRaises(ValueError, base.replace, year=2001)
  1911. def test_astimezone(self):
  1912. return # The rest is no longer applicable
  1913. # Pretty boring! The TZ test is more interesting here. astimezone()
  1914. # simply can't be applied to a naive object.
  1915. dt = self.theclass.now()
  1916. f = FixedOffset(44, "")
  1917. self.assertRaises(ValueError, dt.astimezone) # naive
  1918. self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
  1919. self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
  1920. self.assertRaises(ValueError, dt.astimezone, f) # naive
  1921. self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
  1922. class Bogus(tzinfo):
  1923. def utcoffset(self, dt): return None
  1924. def dst(self, dt): return timedelta(0)
  1925. bog = Bogus()
  1926. self.assertRaises(ValueError, dt.astimezone, bog) # naive
  1927. self.assertRaises(ValueError,
  1928. dt.replace(tzinfo=bog).astimezone, f)
  1929. class AlsoBogus(tzinfo):
  1930. def utcoffset(self, dt): return timedelta(0)
  1931. def dst(self, dt): return None
  1932. alsobog = AlsoBogus()
  1933. self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
  1934. def test_subclass_datetime(self):
  1935. class C(self.theclass):
  1936. theAnswer = 42
  1937. def __new__(cls, *args, **kws):
  1938. temp = kws.copy()
  1939. extra = temp.pop('extra')
  1940. result = self.theclass.__new__(cls, *args, **temp)
  1941. result.extra = extra
  1942. return result
  1943. def newmeth(self, start):
  1944. return start + self.year + self.month + self.second
  1945. args = 2003, 4, 14, 12, 13, 41
  1946. dt1 = self.theclass(*args)
  1947. dt2 = C(*args, **{'extra': 7})
  1948. self.assertEqual(dt2.__class__, C)
  1949. self.assertEqual(dt2.theAnswer, 42)
  1950. self.assertEqual(dt2.extra, 7)
  1951. self.assertEqual(dt1.toordinal(), dt2.toordinal())
  1952. self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
  1953. dt1.second - 7)
  1954. class TestSubclassDateTime(TestDateTime):
  1955. theclass = SubclassDatetime
  1956. # Override tests not designed for subclass
  1957. @unittest.skip('not appropriate for subclasses')
  1958. def test_roundtrip(self):
  1959. pass
  1960. class SubclassTime(time):
  1961. sub_var = 1
  1962. class TestTime(HarmlessMixedComparison, unittest.TestCase):
  1963. theclass = time
  1964. def test_basic_attributes(self):
  1965. t = self.theclass(12, 0)
  1966. self.assertEqual(t.hour, 12)
  1967. self.assertEqual(t.minute, 0)
  1968. self.assertEqual(t.second, 0)
  1969. self.assertEqual(t.microsecond, 0)
  1970. def test_basic_attributes_nonzero(self):
  1971. # Make sure all attributes are non-zero so bugs in
  1972. # bit-shifting access show up.
  1973. t = self.theclass(12, 59, 59, 8000)
  1974. self.assertEqual(t.hour, 12)
  1975. self.assertEqual(t.minute, 59)
  1976. self.assertEqual(t.second, 59)
  1977. self.assertEqual(t.microsecond, 8000)
  1978. def test_roundtrip(self):
  1979. t = self.theclass(1, 2, 3, 4)
  1980. # Verify t -> string -> time identity.
  1981. s = repr(t)
  1982. self.assertTrue(s.startswith('datetime.'))
  1983. s = s[9:]
  1984. t2 = eval(s)
  1985. self.assertEqual(t, t2)
  1986. # Verify identity via reconstructing from pieces.
  1987. t2 = self.theclass(t.hour, t.minute, t.second,
  1988. t.microsecond)
  1989. self.assertEqual(t, t2)
  1990. def test_comparing(self):
  1991. args = [1, 2, 3, 4]
  1992. t1 = self.theclass(*args)
  1993. t2 = self.theclass(*args)
  1994. self.assertEqual(t1, t2)
  1995. self.assertTrue(t1 <= t2)
  1996. self.assertTrue(t1 >= t2)
  1997. self.assertFalse(t1 != t2)
  1998. self.assertFalse(t1 < t2)
  1999. self.assertFalse(t1 > t2)
  2000. for i in range(len(args)):
  2001. newargs = args[:]
  2002. newargs[i] = args[i] + 1
  2003. t2 = self.theclass(*newargs) # this is larger than t1
  2004. self.assertTrue(t1 < t2)
  2005. self.assertTrue(t2 > t1)
  2006. self.assertTrue(t1 <= t2)
  2007. self.assertTrue(t2 >= t1)
  2008. self.assertTrue(t1 != t2)
  2009. self.assertTrue(t2 != t1)
  2010. self.assertFalse(t1 == t2)
  2011. self.assertFalse(t2 == t1)
  2012. self.assertFalse(t1 > t2)
  2013. self.assertFalse(t2 < t1)
  2014. self.assertFalse(t1 >= t2)
  2015. self.assertFalse(t2 <= t1)
  2016. for badarg in OTHERSTUFF:
  2017. self.assertEqual(t1 == badarg, False)
  2018. self.assertEqual(t1 != badarg, True)
  2019. self.assertEqual(badarg == t1, False)
  2020. self.assertEqual(badarg != t1, True)
  2021. self.assertRaises(TypeError, lambda: t1 <= badarg)
  2022. self.assertRaises(TypeError, lambda: t1 < badarg)
  2023. self.assertRaises(TypeError, lambda: t1 > badarg)
  2024. self.assertRaises(TypeError, lambda: t1 >= badarg)
  2025. self.assertRaises(TypeError, lambda: badarg <= t1)
  2026. self.assertRaises(TypeError, lambda: badarg < t1)
  2027. self.assertRaises(TypeError, lambda: badarg > t1)
  2028. self.assertRaises(TypeError, lambda: badarg >= t1)
  2029. def test_bad_constructor_arguments(self):
  2030. # bad hours
  2031. self.theclass(0, 0) # no exception
  2032. self.theclass(23, 0) # no exception
  2033. self.assertRaises(ValueError, self.theclass, -1, 0)
  2034. self.assertRaises(ValueError, self.theclass, 24, 0)
  2035. # bad minutes
  2036. self.theclass(23, 0) # no exception
  2037. self.theclass(23, 59) # no exception
  2038. self.assertRaises(ValueError, self.theclass, 23, -1)
  2039. self.assertRaises(ValueError, self.theclass, 23, 60)
  2040. # bad seconds
  2041. self.theclass(23, 59, 0) # no exception
  2042. self.theclass(23, 59, 59) # no exception
  2043. self.assertRaises(ValueError, self.theclass, 23, 59, -1)
  2044. self.assertRaises(ValueError, self.theclass, 23, 59, 60)
  2045. # bad microseconds
  2046. self.theclass(23, 59, 59, 0) # no exception
  2047. self.theclass(23, 59, 59, 999999) # no exception
  2048. self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
  2049. self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
  2050. def test_hash_equality(self):
  2051. d = self.theclass(23, 30, 17)
  2052. e = self.theclass(23, 30, 17)
  2053. self.assertEqual(d, e)
  2054. self.assertEqual(hash(d), hash(e))
  2055. dic = {d: 1}
  2056. dic[e] = 2
  2057. self.assertEqual(len(dic), 1)
  2058. self.assertEqual(dic[d], 2)
  2059. self.assertEqual(dic[e], 2)
  2060. d = self.theclass(0, 5, 17)
  2061. e = self.theclass(0, 5, 17)
  2062. self.assertEqual(d, e)
  2063. self.assertEqual(hash(d), hash(e))
  2064. dic = {d: 1}
  2065. dic[e] = 2
  2066. self.assertEqual(len(dic), 1)
  2067. self.assertEqual(dic[d], 2)
  2068. self.assertEqual(dic[e], 2)
  2069. def test_isoformat(self):
  2070. t = self.theclass(4, 5, 1, 123)
  2071. self.assertEqual(t.isoformat(), "04:05:01.000123")
  2072. self.assertEqual(t.isoformat(), str(t))
  2073. t = self.theclass()
  2074. self.assertEqual(t.isoformat(), "00:00:00")
  2075. self.assertEqual(t.isoformat(), str(t))
  2076. t = self.theclass(microsecond=1)
  2077. self.assertEqual(t.isoformat(), "00:00:00.000001")
  2078. self.assertEqual(t.isoformat(), str(t))
  2079. t = self.theclass(microsecond=10)
  2080. self.assertEqual(t.isoformat(), "00:00:00.000010")
  2081. self.assertEqual(t.isoformat(), str(t))
  2082. t = self.theclass(microsecond=100)
  2083. self.assertEqual(t.isoformat(), "00:00:00.000100")
  2084. self.assertEqual(t.isoformat(), str(t))
  2085. t = self.theclass(microsecond=1000)
  2086. self.assertEqual(t.isoformat(), "00:00:00.001000")
  2087. self.assertEqual(t.isoformat(), str(t))
  2088. t = self.theclass(microsecond=10000)
  2089. self.assertEqual(t.isoformat(), "00:00:00.010000")
  2090. self.assertEqual(t.isoformat(), str(t))
  2091. t = self.theclass(microsecond=100000)
  2092. self.assertEqual(t.isoformat(), "00:00:00.100000")
  2093. self.assertEqual(t.isoformat(), str(t))
  2094. t = self.theclass(hour=12, minute=34, second=56, microsecond=123456)
  2095. self.assertEqual(t.isoformat(timespec='hours'), "12")
  2096. self.assertEqual(t.isoformat(timespec='minutes'), "12:34")
  2097. self.assertEqual(t.isoformat(timespec='seconds'), "12:34:56")
  2098. self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.123")
  2099. self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.123456")
  2100. self.assertEqual(t.isoformat(timespec='auto'), "12:34:56.123456")
  2101. self.assertRaises(ValueError, t.isoformat, timespec='monkey')
  2102. t = self.theclass(hour=12, minute=34, second=56, microsecond=999500)
  2103. self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.999")
  2104. t = self.theclass(hour=12, minute=34, second=56, microsecond=0)
  2105. self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.000")
  2106. self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.000000")
  2107. self.assertEqual(t.isoformat(timespec='auto'), "12:34:56")
  2108. def test_1653736(self):
  2109. # verify it doesn't accept extra keyword arguments
  2110. t = self.theclass(second=1)
  2111. self.assertRaises(TypeError, t.isoformat, foo=3)
  2112. def test_strftime(self):
  2113. t = self.theclass(1, 2, 3, 4)
  2114. self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
  2115. # A naive object replaces %z and %Z with empty strings.
  2116. self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
  2117. def test_format(self):
  2118. t = self.theclass(1, 2, 3, 4)
  2119. self.assertEqual(t.__format__(''), str(t))
  2120. with self.assertRaisesRegex(TypeError, 'must be str, not int'):
  2121. t.__format__(123)
  2122. # check that a derived class's __str__() gets called
  2123. class A(self.theclass):
  2124. def __str__(self):
  2125. return 'A'
  2126. a = A(1, 2, 3, 4)
  2127. self.assertEqual(a.__format__(''), 'A')
  2128. # check that a derived class's strftime gets called
  2129. class B(self.theclass):
  2130. def strftime(self, format_spec):
  2131. return 'B'
  2132. b = B(1, 2, 3, 4)
  2133. self.assertEqual(b.__format__(''), str(t))
  2134. for fmt in ['%H %M %S',
  2135. ]:
  2136. self.assertEqual(t.__format__(fmt), t.strftime(fmt))
  2137. self.assertEqual(a.__format__(fmt), t.strftime(fmt))
  2138. self.assertEqual(b.__format__(fmt), 'B')
  2139. def test_str(self):
  2140. self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
  2141. self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
  2142. self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
  2143. self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
  2144. self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
  2145. def test_repr(self):
  2146. name = 'datetime.' + self.theclass.__name__
  2147. self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
  2148. "%s(1, 2, 3, 4)" % name)
  2149. self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
  2150. "%s(10, 2, 3, 4000)" % name)
  2151. self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
  2152. "%s(0, 2, 3, 400000)" % name)
  2153. self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
  2154. "%s(12, 2, 3)" % name)
  2155. self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
  2156. "%s(23, 15)" % name)
  2157. def test_resolution_info(self):
  2158. self.assertIsInstance(self.theclass.min, self.theclass)
  2159. self.assertIsInstance(self.theclass.max, self.theclass)
  2160. self.assertIsInstance(self.theclass.resolution, timedelta)
  2161. self.assertTrue(self.theclass.max > self.theclass.min)
  2162. def test_pickling(self):
  2163. args = 20, 59, 16, 64**2
  2164. orig = self.theclass(*args)
  2165. for pickler, unpickler, proto in pickle_choices:
  2166. green = pickler.dumps(orig, proto)
  2167. derived = unpickler.loads(green)
  2168. self.assertEqual(orig, derived)
  2169. self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
  2170. def test_pickling_subclass_time(self):
  2171. args = 20, 59, 16, 64**2
  2172. orig = SubclassTime(*args)
  2173. for pickler, unpickler, proto in pickle_choices:
  2174. green = pickler.dumps(orig, proto)
  2175. derived = unpickler.loads(green)
  2176. self.assertEqual(orig, derived)
  2177. def test_bool(self):
  2178. # time is always True.
  2179. cls = self.theclass
  2180. self.assertTrue(cls(1))
  2181. self.assertTrue(cls(0, 1))
  2182. self.assertTrue(cls(0, 0, 1))
  2183. self.assertTrue(cls(0, 0, 0, 1))
  2184. self.assertTrue(cls(0))
  2185. self.assertTrue(cls())
  2186. def test_replace(self):
  2187. cls = self.theclass
  2188. args = [1, 2, 3, 4]
  2189. base = cls(*args)
  2190. self.assertEqual(base, base.replace())
  2191. i = 0
  2192. for name, newval in (("hour", 5),
  2193. ("minute", 6),
  2194. ("second", 7),
  2195. ("microsecond", 8)):
  2196. newargs = args[:]
  2197. newargs[i] = newval
  2198. expected = cls(*newargs)
  2199. got = base.replace(**{name: newval})
  2200. self.assertEqual(expected, got)
  2201. i += 1
  2202. # Out of bounds.
  2203. base = cls(1)
  2204. self.assertRaises(ValueError, base.replace, hour=24)
  2205. self.assertRaises(ValueError, base.replace, minute=-1)
  2206. self.assertRaises(ValueError, base.replace, second=100)
  2207. self.assertRaises(ValueError, base.replace, microsecond=1000000)
  2208. def test_subclass_time(self):
  2209. class C(self.theclass):
  2210. theAnswer = 42
  2211. def __new__(cls, *args, **kws):
  2212. temp = kws.copy()
  2213. extra = temp.pop('extra')
  2214. result = self.theclass.__new__(cls, *args, **temp)
  2215. result.extra = extra
  2216. return result
  2217. def newmeth(self, start):
  2218. return start + self.hour + self.second
  2219. args = 4, 5, 6
  2220. dt1 = self.theclass(*args)
  2221. dt2 = C(*args, **{'extra': 7})
  2222. self.assertEqual(dt2.__class__, C)
  2223. self.assertEqual(dt2.theAnswer, 42)
  2224. self.assertEqual(dt2.extra, 7)
  2225. self.assertEqual(dt1.isoformat(), dt2.isoformat())
  2226. self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
  2227. def test_backdoor_resistance(self):
  2228. # see TestDate.test_backdoor_resistance().
  2229. base = '2:59.0'
  2230. for hour_byte in ' ', '9', chr(24), '\xff':
  2231. self.assertRaises(TypeError, self.theclass,
  2232. hour_byte + base[1:])
  2233. # Good bytes, but bad tzinfo:
  2234. with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
  2235. self.theclass(bytes([1] * len(base)), 'EST')
  2236. # A mixin for classes with a tzinfo= argument. Subclasses must define
  2237. # theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever)
  2238. # must be legit (which is true for time and datetime).
  2239. class TZInfoBase:
  2240. def test_argument_passing(self):
  2241. cls = self.theclass
  2242. # A datetime passes itself on, a time passes None.
  2243. class introspective(tzinfo):
  2244. def tzname(self, dt): return dt and "real" or "none"
  2245. def utcoffset(self, dt):
  2246. return timedelta(minutes = dt and 42 or -42)
  2247. dst = utcoffset
  2248. obj = cls(1, 2, 3, tzinfo=introspective())
  2249. expected = cls is time and "none" or "real"
  2250. self.assertEqual(obj.tzname(), expected)
  2251. expected = timedelta(minutes=(cls is time and -42 or 42))
  2252. self.assertEqual(obj.utcoffset(), expected)
  2253. self.assertEqual(obj.dst(), expected)
  2254. def test_bad_tzinfo_classes(self):
  2255. cls = self.theclass
  2256. self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
  2257. class NiceTry(object):
  2258. def __init__(self): pass
  2259. def utcoffset(self, dt): pass
  2260. self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
  2261. class BetterTry(tzinfo):
  2262. def __init__(self): pass
  2263. def utcoffset(self, dt): pass
  2264. b = BetterTry()
  2265. t = cls(1, 1, 1, tzinfo=b)
  2266. self.assertIs(t.tzinfo, b)
  2267. def test_utc_offset_out_of_bounds(self):
  2268. class Edgy(tzinfo):
  2269. def __init__(self, offset):
  2270. self.offset = timedelta(minutes=offset)
  2271. def utcoffset(self, dt):
  2272. return self.offset
  2273. cls = self.theclass
  2274. for offset, legit in ((-1440, False),
  2275. (-1439, True),
  2276. (1439, True),
  2277. (1440, False)):
  2278. if cls is time:
  2279. t = cls(1, 2, 3, tzinfo=Edgy(offset))
  2280. elif cls is datetime:
  2281. t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
  2282. else:
  2283. assert 0, "impossible"
  2284. if legit:
  2285. aofs = abs(offset)
  2286. h, m = divmod(aofs, 60)
  2287. tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
  2288. if isinstance(t, datetime):
  2289. t = t.timetz()
  2290. self.assertEqual(str(t), "01:02:03" + tag)
  2291. else:
  2292. self.assertRaises(ValueError, str, t)
  2293. def test_tzinfo_classes(self):
  2294. cls = self.theclass
  2295. class C1(tzinfo):
  2296. def utcoffset(self, dt): return None
  2297. def dst(self, dt): return None
  2298. def tzname(self, dt): return None
  2299. for t in (cls(1, 1, 1),
  2300. cls(1, 1, 1, tzinfo=None),
  2301. cls(1, 1, 1, tzinfo=C1())):
  2302. self.assertIsNone(t.utcoffset())
  2303. self.assertIsNone(t.dst())
  2304. self.assertIsNone(t.tzname())
  2305. class C3(tzinfo):
  2306. def utcoffset(self, dt): return timedelta(minutes=-1439)
  2307. def dst(self, dt): return timedelta(minutes=1439)
  2308. def tzname(self, dt): return "aname"
  2309. t = cls(1, 1, 1, tzinfo=C3())
  2310. self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
  2311. self.assertEqual(t.dst(), timedelta(minutes=1439))
  2312. self.assertEqual(t.tzname(), "aname")
  2313. # Wrong types.
  2314. class C4(tzinfo):
  2315. def utcoffset(self, dt): return "aname"
  2316. def dst(self, dt): return 7
  2317. def tzname(self, dt): return 0
  2318. t = cls(1, 1, 1, tzinfo=C4())
  2319. self.assertRaises(TypeError, t.utcoffset)
  2320. self.assertRaises(TypeError, t.dst)
  2321. self.assertRaises(TypeError, t.tzname)
  2322. # Offset out of range.
  2323. class C6(tzinfo):
  2324. def utcoffset(self, dt): return timedelta(hours=-24)
  2325. def dst(self, dt): return timedelta(hours=24)
  2326. t = cls(1, 1, 1, tzinfo=C6())
  2327. self.assertRaises(ValueError, t.utcoffset)
  2328. self.assertRaises(ValueError, t.dst)
  2329. # Not a whole number of seconds.
  2330. class C7(tzinfo):
  2331. def utcoffset(self, dt): return timedelta(microseconds=61)
  2332. def dst(self, dt): return timedelta(microseconds=-81)
  2333. t = cls(1, 1, 1, tzinfo=C7())
  2334. self.assertEqual(t.utcoffset(), timedelta(microseconds=61))
  2335. self.assertEqual(t.dst(), timedelta(microseconds=-81))
  2336. def test_aware_compare(self):
  2337. cls = self.theclass
  2338. # Ensure that utcoffset() gets ignored if the comparands have
  2339. # the same tzinfo member.
  2340. class OperandDependentOffset(tzinfo):
  2341. def utcoffset(self, t):
  2342. if t.minute < 10:
  2343. # d0 and d1 equal after adjustment
  2344. return timedelta(minutes=t.minute)
  2345. else:
  2346. # d2 off in the weeds
  2347. return timedelta(minutes=59)
  2348. base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
  2349. d0 = base.replace(minute=3)
  2350. d1 = base.replace(minute=9)
  2351. d2 = base.replace(minute=11)
  2352. for x in d0, d1, d2:
  2353. for y in d0, d1, d2:
  2354. for op in lt, le, gt, ge, eq, ne:
  2355. got = op(x, y)
  2356. expected = op(x.minute, y.minute)
  2357. self.assertEqual(got, expected)
  2358. # However, if they're different members, uctoffset is not ignored.
  2359. # Note that a time can't actually have an operand-depedent offset,
  2360. # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
  2361. # so skip this test for time.
  2362. if cls is not time:
  2363. d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
  2364. d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
  2365. d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
  2366. for x in d0, d1, d2:
  2367. for y in d0, d1, d2:
  2368. got = (x > y) - (x < y)
  2369. if (x is d0 or x is d1) and (y is d0 or y is d1):
  2370. expected = 0
  2371. elif x is y is d2:
  2372. expected = 0
  2373. elif x is d2:
  2374. expected = -1
  2375. else:
  2376. assert y is d2
  2377. expected = 1
  2378. self.assertEqual(got, expected)
  2379. # Testing time objects with a non-None tzinfo.
  2380. class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
  2381. theclass = time
  2382. def test_empty(self):
  2383. t = self.theclass()
  2384. self.assertEqual(t.hour, 0)
  2385. self.assertEqual(t.minute, 0)
  2386. self.assertEqual(t.second, 0)
  2387. self.assertEqual(t.microsecond, 0)
  2388. self.assertIsNone(t.tzinfo)
  2389. def test_zones(self):
  2390. est = FixedOffset(-300, "EST", 1)
  2391. utc = FixedOffset(0, "UTC", -2)
  2392. met = FixedOffset(60, "MET", 3)
  2393. t1 = time( 7, 47, tzinfo=est)
  2394. t2 = time(12, 47, tzinfo=utc)
  2395. t3 = time(13, 47, tzinfo=met)
  2396. t4 = time(microsecond=40)
  2397. t5 = time(microsecond=40, tzinfo=utc)
  2398. self.assertEqual(t1.tzinfo, est)
  2399. self.assertEqual(t2.tzinfo, utc)
  2400. self.assertEqual(t3.tzinfo, met)
  2401. self.assertIsNone(t4.tzinfo)
  2402. self.assertEqual(t5.tzinfo, utc)
  2403. self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
  2404. self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
  2405. self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
  2406. self.assertIsNone(t4.utcoffset())
  2407. self.assertRaises(TypeError, t1.utcoffset, "no args")
  2408. self.assertEqual(t1.tzname(), "EST")
  2409. self.assertEqual(t2.tzname(), "UTC")
  2410. self.assertEqual(t3.tzname(), "MET")
  2411. self.assertIsNone(t4.tzname())
  2412. self.assertRaises(TypeError, t1.tzname, "no args")
  2413. self.assertEqual(t1.dst(), timedelta(minutes=1))
  2414. self.assertEqual(t2.dst(), timedelta(minutes=-2))
  2415. self.assertEqual(t3.dst(), timedelta(minutes=3))
  2416. self.assertIsNone(t4.dst())
  2417. self.assertRaises(TypeError, t1.dst, "no args")
  2418. self.assertEqual(hash(t1), hash(t2))
  2419. self.assertEqual(hash(t1), hash(t3))
  2420. self.assertEqual(hash(t2), hash(t3))
  2421. self.assertEqual(t1, t2)
  2422. self.assertEqual(t1, t3)
  2423. self.assertEqual(t2, t3)
  2424. self.assertNotEqual(t4, t5) # mixed tz-aware & naive
  2425. self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
  2426. self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
  2427. self.assertEqual(str(t1), "07:47:00-05:00")
  2428. self.assertEqual(str(t2), "12:47:00+00:00")
  2429. self.assertEqual(str(t3), "13:47:00+01:00")
  2430. self.assertEqual(str(t4), "00:00:00.000040")
  2431. self.assertEqual(str(t5), "00:00:00.000040+00:00")
  2432. self.assertEqual(t1.isoformat(), "07:47:00-05:00")
  2433. self.assertEqual(t2.isoformat(), "12:47:00+00:00")
  2434. self.assertEqual(t3.isoformat(), "13:47:00+01:00")
  2435. self.assertEqual(t4.isoformat(), "00:00:00.000040")
  2436. self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
  2437. d = 'datetime.time'
  2438. self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
  2439. self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
  2440. self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
  2441. self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
  2442. self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
  2443. self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
  2444. "07:47:00 %Z=EST %z=-0500")
  2445. self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
  2446. self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
  2447. yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
  2448. t1 = time(23, 59, tzinfo=yuck)
  2449. self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
  2450. "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
  2451. # Check that an invalid tzname result raises an exception.
  2452. class Badtzname(tzinfo):
  2453. tz = 42
  2454. def tzname(self, dt): return self.tz
  2455. t = time(2, 3, 4, tzinfo=Badtzname())
  2456. self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
  2457. self.assertRaises(TypeError, t.strftime, "%Z")
  2458. # Issue #6697:
  2459. if '_Fast' in self.__class__.__name__:
  2460. Badtzname.tz = '\ud800'
  2461. self.assertRaises(ValueError, t.strftime, "%Z")
  2462. def test_hash_edge_cases(self):
  2463. # Offsets that overflow a basic time.
  2464. t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
  2465. t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
  2466. self.assertEqual(hash(t1), hash(t2))
  2467. t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
  2468. t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
  2469. self.assertEqual(hash(t1), hash(t2))
  2470. def test_pickling(self):
  2471. # Try one without a tzinfo.
  2472. args = 20, 59, 16, 64**2
  2473. orig = self.theclass(*args)
  2474. for pickler, unpickler, proto in pickle_choices:
  2475. green = pickler.dumps(orig, proto)
  2476. derived = unpickler.loads(green)
  2477. self.assertEqual(orig, derived)
  2478. self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
  2479. # Try one with a tzinfo.
  2480. tinfo = PicklableFixedOffset(-300, 'cookie')
  2481. orig = self.theclass(5, 6, 7, tzinfo=tinfo)
  2482. for pickler, unpickler, proto in pickle_choices:
  2483. green = pickler.dumps(orig, proto)
  2484. derived = unpickler.loads(green)
  2485. self.assertEqual(orig, derived)
  2486. self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
  2487. self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
  2488. self.assertEqual(derived.tzname(), 'cookie')
  2489. self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
  2490. def test_more_bool(self):
  2491. # time is always True.
  2492. cls = self.theclass
  2493. t = cls(0, tzinfo=FixedOffset(-300, ""))
  2494. self.assertTrue(t)
  2495. t = cls(5, tzinfo=FixedOffset(-300, ""))
  2496. self.assertTrue(t)
  2497. t = cls(5, tzinfo=FixedOffset(300, ""))
  2498. self.assertTrue(t)
  2499. t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
  2500. self.assertTrue(t)
  2501. def test_replace(self):
  2502. cls = self.theclass
  2503. z100 = FixedOffset(100, "+100")
  2504. zm200 = FixedOffset(timedelta(minutes=-200), "-200")
  2505. args = [1, 2, 3, 4, z100]
  2506. base = cls(*args)
  2507. self.assertEqual(base, base.replace())
  2508. i = 0
  2509. for name, newval in (("hour", 5),
  2510. ("minute", 6),
  2511. ("second", 7),
  2512. ("microsecond", 8),
  2513. ("tzinfo", zm200)):
  2514. newargs = args[:]
  2515. newargs[i] = newval
  2516. expected = cls(*newargs)
  2517. got = base.replace(**{name: newval})
  2518. self.assertEqual(expected, got)
  2519. i += 1
  2520. # Ensure we can get rid of a tzinfo.
  2521. self.assertEqual(base.tzname(), "+100")
  2522. base2 = base.replace(tzinfo=None)
  2523. self.assertIsNone(base2.tzinfo)
  2524. self.assertIsNone(base2.tzname())
  2525. # Ensure we can add one.
  2526. base3 = base2.replace(tzinfo=z100)
  2527. self.assertEqual(base, base3)
  2528. self.assertIs(base.tzinfo, base3.tzinfo)
  2529. # Out of bounds.
  2530. base = cls(1)
  2531. self.assertRaises(ValueError, base.replace, hour=24)
  2532. self.assertRaises(ValueError, base.replace, minute=-1)
  2533. self.assertRaises(ValueError, base.replace, second=100)
  2534. self.assertRaises(ValueError, base.replace, microsecond=1000000)
  2535. def test_mixed_compare(self):
  2536. t1 = time(1, 2, 3)
  2537. t2 = time(1, 2, 3)
  2538. self.assertEqual(t1, t2)
  2539. t2 = t2.replace(tzinfo=None)
  2540. self.assertEqual(t1, t2)
  2541. t2 = t2.replace(tzinfo=FixedOffset(None, ""))
  2542. self.assertEqual(t1, t2)
  2543. t2 = t2.replace(tzinfo=FixedOffset(0, ""))
  2544. self.assertNotEqual(t1, t2)
  2545. # In time w/ identical tzinfo objects, utcoffset is ignored.
  2546. class Varies(tzinfo):
  2547. def __init__(self):
  2548. self.offset = timedelta(minutes=22)
  2549. def utcoffset(self, t):
  2550. self.offset += timedelta(minutes=1)
  2551. return self.offset
  2552. v = Varies()
  2553. t1 = t2.replace(tzinfo=v)
  2554. t2 = t2.replace(tzinfo=v)
  2555. self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
  2556. self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
  2557. self.assertEqual(t1, t2)
  2558. # But if they're not identical, it isn't ignored.
  2559. t2 = t2.replace(tzinfo=Varies())
  2560. self.assertTrue(t1 < t2) # t1's offset counter still going up
  2561. def test_subclass_timetz(self):
  2562. class C(self.theclass):
  2563. theAnswer = 42
  2564. def __new__(cls, *args, **kws):
  2565. temp = kws.copy()
  2566. extra = temp.pop('extra')
  2567. result = self.theclass.__new__(cls, *args, **temp)
  2568. result.extra = extra
  2569. return result
  2570. def newmeth(self, start):
  2571. return start + self.hour + self.second
  2572. args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
  2573. dt1 = self.theclass(*args)
  2574. dt2 = C(*args, **{'extra': 7})
  2575. self.assertEqual(dt2.__class__, C)
  2576. self.assertEqual(dt2.theAnswer, 42)
  2577. self.assertEqual(dt2.extra, 7)
  2578. self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
  2579. self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
  2580. # Testing datetime objects with a non-None tzinfo.
  2581. class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
  2582. theclass = datetime
  2583. def test_trivial(self):
  2584. dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
  2585. self.assertEqual(dt.year, 1)
  2586. self.assertEqual(dt.month, 2)
  2587. self.assertEqual(dt.day, 3)
  2588. self.assertEqual(dt.hour, 4)
  2589. self.assertEqual(dt.minute, 5)
  2590. self.assertEqual(dt.second, 6)
  2591. self.assertEqual(dt.microsecond, 7)
  2592. self.assertEqual(dt.tzinfo, None)
  2593. def test_even_more_compare(self):
  2594. # The test_compare() and test_more_compare() inherited from TestDate
  2595. # and TestDateTime covered non-tzinfo cases.
  2596. # Smallest possible after UTC adjustment.
  2597. t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
  2598. # Largest possible after UTC adjustment.
  2599. t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
  2600. tzinfo=FixedOffset(-1439, ""))
  2601. # Make sure those compare correctly, and w/o overflow.
  2602. self.assertTrue(t1 < t2)
  2603. self.assertTrue(t1 != t2)
  2604. self.assertTrue(t2 > t1)
  2605. self.assertEqual(t1, t1)
  2606. self.assertEqual(t2, t2)
  2607. # Equal afer adjustment.
  2608. t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
  2609. t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
  2610. self.assertEqual(t1, t2)
  2611. # Change t1 not to subtract a minute, and t1 should be larger.
  2612. t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
  2613. self.assertTrue(t1 > t2)
  2614. # Change t1 to subtract 2 minutes, and t1 should be smaller.
  2615. t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
  2616. self.assertTrue(t1 < t2)
  2617. # Back to the original t1, but make seconds resolve it.
  2618. t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
  2619. second=1)
  2620. self.assertTrue(t1 > t2)
  2621. # Likewise, but make microseconds resolve it.
  2622. t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
  2623. microsecond=1)
  2624. self.assertTrue(t1 > t2)
  2625. # Make t2 naive and it should differ.
  2626. t2 = self.theclass.min
  2627. self.assertNotEqual(t1, t2)
  2628. self.assertEqual(t2, t2)
  2629. # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
  2630. class Naive(tzinfo):
  2631. def utcoffset(self, dt): return None
  2632. t2 = self.theclass(5, 6, 7, tzinfo=Naive())
  2633. self.assertNotEqual(t1, t2)
  2634. self.assertEqual(t2, t2)
  2635. # OTOH, it's OK to compare two of these mixing the two ways of being
  2636. # naive.
  2637. t1 = self.theclass(5, 6, 7)
  2638. self.assertEqual(t1, t2)
  2639. # Try a bogus uctoffset.
  2640. class Bogus(tzinfo):
  2641. def utcoffset(self, dt):
  2642. return timedelta(minutes=1440) # out of bounds
  2643. t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
  2644. t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
  2645. self.assertRaises(ValueError, lambda: t1 == t2)
  2646. def test_pickling(self):
  2647. # Try one without a tzinfo.
  2648. args = 6, 7, 23, 20, 59, 1, 64**2
  2649. orig = self.theclass(*args)
  2650. for pickler, unpickler, proto in pickle_choices:
  2651. green = pickler.dumps(orig, proto)
  2652. derived = unpickler.loads(green)
  2653. self.assertEqual(orig, derived)
  2654. self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
  2655. # Try one with a tzinfo.
  2656. tinfo = PicklableFixedOffset(-300, 'cookie')
  2657. orig = self.theclass(*args, **{'tzinfo': tinfo})
  2658. derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
  2659. for pickler, unpickler, proto in pickle_choices:
  2660. green = pickler.dumps(orig, proto)
  2661. derived = unpickler.loads(green)
  2662. self.assertEqual(orig, derived)
  2663. self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
  2664. self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
  2665. self.assertEqual(derived.tzname(), 'cookie')
  2666. self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
  2667. def test_extreme_hashes(self):
  2668. # If an attempt is made to hash these via subtracting the offset
  2669. # then hashing a datetime object, OverflowError results. The
  2670. # Python implementation used to blow up here.
  2671. t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
  2672. hash(t)
  2673. t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
  2674. tzinfo=FixedOffset(-1439, ""))
  2675. hash(t)
  2676. # OTOH, an OOB offset should blow up.
  2677. t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
  2678. self.assertRaises(ValueError, hash, t)
  2679. def test_zones(self):
  2680. est = FixedOffset(-300, "EST")
  2681. utc = FixedOffset(0, "UTC")
  2682. met = FixedOffset(60, "MET")
  2683. t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
  2684. t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
  2685. t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
  2686. self.assertEqual(t1.tzinfo, est)
  2687. self.assertEqual(t2.tzinfo, utc)
  2688. self.assertEqual(t3.tzinfo, met)
  2689. self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
  2690. self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
  2691. self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
  2692. self.assertEqual(t1.tzname(), "EST")
  2693. self.assertEqual(t2.tzname(), "UTC")
  2694. self.assertEqual(t3.tzname(), "MET")
  2695. self.assertEqual(hash(t1), hash(t2))
  2696. self.assertEqual(hash(t1), hash(t3))
  2697. self.assertEqual(hash(t2), hash(t3))
  2698. self.assertEqual(t1, t2)
  2699. self.assertEqual(t1, t3)
  2700. self.assertEqual(t2, t3)
  2701. self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
  2702. self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
  2703. self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
  2704. d = 'datetime.datetime(2002, 3, 19, '
  2705. self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
  2706. self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
  2707. self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
  2708. def test_combine(self):
  2709. met = FixedOffset(60, "MET")
  2710. d = date(2002, 3, 4)
  2711. tz = time(18, 45, 3, 1234, tzinfo=met)
  2712. dt = datetime.combine(d, tz)
  2713. self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
  2714. tzinfo=met))
  2715. def test_extract(self):
  2716. met = FixedOffset(60, "MET")
  2717. dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
  2718. self.assertEqual(dt.date(), date(2002, 3, 4))
  2719. self.assertEqual(dt.time(), time(18, 45, 3, 1234))
  2720. self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
  2721. def test_tz_aware_arithmetic(self):
  2722. import random
  2723. now = self.theclass.now()
  2724. tz55 = FixedOffset(-330, "west 5:30")
  2725. timeaware = now.time().replace(tzinfo=tz55)
  2726. nowaware = self.theclass.combine(now.date(), timeaware)
  2727. self.assertIs(nowaware.tzinfo, tz55)
  2728. self.assertEqual(nowaware.timetz(), timeaware)
  2729. # Can't mix aware and non-aware.
  2730. self.assertRaises(TypeError, lambda: now - nowaware)
  2731. self.assertRaises(TypeError, lambda: nowaware - now)
  2732. # And adding datetime's doesn't make sense, aware or not.
  2733. self.assertRaises(TypeError, lambda: now + nowaware)
  2734. self.assertRaises(TypeError, lambda: nowaware + now)
  2735. self.assertRaises(TypeError, lambda: nowaware + nowaware)
  2736. # Subtracting should yield 0.
  2737. self.assertEqual(now - now, timedelta(0))
  2738. self.assertEqual(nowaware - nowaware, timedelta(0))
  2739. # Adding a delta should preserve tzinfo.
  2740. delta = timedelta(weeks=1, minutes=12, microseconds=5678)
  2741. nowawareplus = nowaware + delta
  2742. self.assertIs(nowaware.tzinfo, tz55)
  2743. nowawareplus2 = delta + nowaware
  2744. self.assertIs(nowawareplus2.tzinfo, tz55)
  2745. self.assertEqual(nowawareplus, nowawareplus2)
  2746. # that - delta should be what we started with, and that - what we
  2747. # started with should be delta.
  2748. diff = nowawareplus - delta
  2749. self.assertIs(diff.tzinfo, tz55)
  2750. self.assertEqual(nowaware, diff)
  2751. self.assertRaises(TypeError, lambda: delta - nowawareplus)
  2752. self.assertEqual(nowawareplus - nowaware, delta)
  2753. # Make up a random timezone.
  2754. tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
  2755. # Attach it to nowawareplus.
  2756. nowawareplus = nowawareplus.replace(tzinfo=tzr)
  2757. self.assertIs(nowawareplus.tzinfo, tzr)
  2758. # Make sure the difference takes the timezone adjustments into account.
  2759. got = nowaware - nowawareplus
  2760. # Expected: (nowaware base - nowaware offset) -
  2761. # (nowawareplus base - nowawareplus offset) =
  2762. # (nowaware base - nowawareplus base) +
  2763. # (nowawareplus offset - nowaware offset) =
  2764. # -delta + nowawareplus offset - nowaware offset
  2765. expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
  2766. self.assertEqual(got, expected)
  2767. # Try max possible difference.
  2768. min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
  2769. max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
  2770. tzinfo=FixedOffset(-1439, "max"))
  2771. maxdiff = max - min
  2772. self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
  2773. timedelta(minutes=2*1439))
  2774. # Different tzinfo, but the same offset
  2775. tza = timezone(HOUR, 'A')
  2776. tzb = timezone(HOUR, 'B')
  2777. delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
  2778. self.assertEqual(delta, self.theclass.min - self.theclass.max)
  2779. def test_tzinfo_now(self):
  2780. meth = self.theclass.now
  2781. # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
  2782. base = meth()
  2783. # Try with and without naming the keyword.
  2784. off42 = FixedOffset(42, "42")
  2785. another = meth(off42)
  2786. again = meth(tz=off42)
  2787. self.assertIs(another.tzinfo, again.tzinfo)
  2788. self.assertEqual(another.utcoffset(), timedelta(minutes=42))
  2789. # Bad argument with and w/o naming the keyword.
  2790. self.assertRaises(TypeError, meth, 16)
  2791. self.assertRaises(TypeError, meth, tzinfo=16)
  2792. # Bad keyword name.
  2793. self.assertRaises(TypeError, meth, tinfo=off42)
  2794. # Too many args.
  2795. self.assertRaises(TypeError, meth, off42, off42)
  2796. # We don't know which time zone we're in, and don't have a tzinfo
  2797. # class to represent it, so seeing whether a tz argument actually
  2798. # does a conversion is tricky.
  2799. utc = FixedOffset(0, "utc", 0)
  2800. for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
  2801. timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
  2802. for dummy in range(3):
  2803. now = datetime.now(weirdtz)
  2804. self.assertIs(now.tzinfo, weirdtz)
  2805. utcnow = datetime.utcnow().replace(tzinfo=utc)
  2806. now2 = utcnow.astimezone(weirdtz)
  2807. if abs(now - now2) < timedelta(seconds=30):
  2808. break
  2809. # Else the code is broken, or more than 30 seconds passed between
  2810. # calls; assuming the latter, just try again.
  2811. else:
  2812. # Three strikes and we're out.
  2813. self.fail("utcnow(), now(tz), or astimezone() may be broken")
  2814. def test_tzinfo_fromtimestamp(self):
  2815. import time
  2816. meth = self.theclass.fromtimestamp
  2817. ts = time.time()
  2818. # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
  2819. base = meth(ts)
  2820. # Try with and without naming the keyword.
  2821. off42 = FixedOffset(42, "42")
  2822. another = meth(ts, off42)
  2823. again = meth(ts, tz=off42)
  2824. self.assertIs(another.tzinfo, again.tzinfo)
  2825. self.assertEqual(another.utcoffset(), timedelta(minutes=42))
  2826. # Bad argument with and w/o naming the keyword.
  2827. self.assertRaises(TypeError, meth, ts, 16)
  2828. self.assertRaises(TypeError, meth, ts, tzinfo=16)
  2829. # Bad keyword name.
  2830. self.assertRaises(TypeError, meth, ts, tinfo=off42)
  2831. # Too many args.
  2832. self.assertRaises(TypeError, meth, ts, off42, off42)
  2833. # Too few args.
  2834. self.assertRaises(TypeError, meth)
  2835. # Try to make sure tz= actually does some conversion.
  2836. timestamp = 1000000000
  2837. utcdatetime = datetime.utcfromtimestamp(timestamp)
  2838. # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
  2839. # But on some flavor of Mac, it's nowhere near that. So we can't have
  2840. # any idea here what time that actually is, we can only test that
  2841. # relative changes match.
  2842. utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
  2843. tz = FixedOffset(utcoffset, "tz", 0)
  2844. expected = utcdatetime + utcoffset
  2845. got = datetime.fromtimestamp(timestamp, tz)
  2846. self.assertEqual(expected, got.replace(tzinfo=None))
  2847. def test_tzinfo_utcnow(self):
  2848. meth = self.theclass.utcnow
  2849. # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
  2850. base = meth()
  2851. # Try with and without naming the keyword; for whatever reason,
  2852. # utcnow() doesn't accept a tzinfo argument.
  2853. off42 = FixedOffset(42, "42")
  2854. self.assertRaises(TypeError, meth, off42)
  2855. self.assertRaises(TypeError, meth, tzinfo=off42)
  2856. def test_tzinfo_utcfromtimestamp(self):
  2857. import time
  2858. meth = self.theclass.utcfromtimestamp
  2859. ts = time.time()
  2860. # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
  2861. base = meth(ts)
  2862. # Try with and without naming the keyword; for whatever reason,
  2863. # utcfromtimestamp() doesn't accept a tzinfo argument.
  2864. off42 = FixedOffset(42, "42")
  2865. self.assertRaises(TypeError, meth, ts, off42)
  2866. self.assertRaises(TypeError, meth, ts, tzinfo=off42)
  2867. def test_tzinfo_timetuple(self):
  2868. # TestDateTime tested most of this. datetime adds a twist to the
  2869. # DST flag.
  2870. class DST(tzinfo):
  2871. def __init__(self, dstvalue):
  2872. if isinstance(dstvalue, int):
  2873. dstvalue = timedelta(minutes=dstvalue)
  2874. self.dstvalue = dstvalue
  2875. def dst(self, dt):
  2876. return self.dstvalue
  2877. cls = self.theclass
  2878. for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
  2879. d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
  2880. t = d.timetuple()
  2881. self.assertEqual(1, t.tm_year)
  2882. self.assertEqual(1, t.tm_mon)
  2883. self.assertEqual(1, t.tm_mday)
  2884. self.assertEqual(10, t.tm_hour)
  2885. self.assertEqual(20, t.tm_min)
  2886. self.assertEqual(30, t.tm_sec)
  2887. self.assertEqual(0, t.tm_wday)
  2888. self.assertEqual(1, t.tm_yday)
  2889. self.assertEqual(flag, t.tm_isdst)
  2890. # dst() returns wrong type.
  2891. self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
  2892. # dst() at the edge.
  2893. self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
  2894. self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
  2895. # dst() out of range.
  2896. self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
  2897. self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
  2898. def test_utctimetuple(self):
  2899. class DST(tzinfo):
  2900. def __init__(self, dstvalue=0):
  2901. if isinstance(dstvalue, int):
  2902. dstvalue = timedelta(minutes=dstvalue)
  2903. self.dstvalue = dstvalue
  2904. def dst(self, dt):
  2905. return self.dstvalue
  2906. cls = self.theclass
  2907. # This can't work: DST didn't implement utcoffset.
  2908. self.assertRaises(NotImplementedError,
  2909. cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
  2910. class UOFS(DST):
  2911. def __init__(self, uofs, dofs=None):
  2912. DST.__init__(self, dofs)
  2913. self.uofs = timedelta(minutes=uofs)
  2914. def utcoffset(self, dt):
  2915. return self.uofs
  2916. for dstvalue in -33, 33, 0, None:
  2917. d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
  2918. t = d.utctimetuple()
  2919. self.assertEqual(d.year, t.tm_year)
  2920. self.assertEqual(d.month, t.tm_mon)
  2921. self.assertEqual(d.day, t.tm_mday)
  2922. self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
  2923. self.assertEqual(13, t.tm_min)
  2924. self.assertEqual(d.second, t.tm_sec)
  2925. self.assertEqual(d.weekday(), t.tm_wday)
  2926. self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
  2927. t.tm_yday)
  2928. # Ensure tm_isdst is 0 regardless of what dst() says: DST
  2929. # is never in effect for a UTC time.
  2930. self.assertEqual(0, t.tm_isdst)
  2931. # For naive datetime, utctimetuple == timetuple except for isdst
  2932. d = cls(1, 2, 3, 10, 20, 30, 40)
  2933. t = d.utctimetuple()
  2934. self.assertEqual(t[:-1], d.timetuple()[:-1])
  2935. self.assertEqual(0, t.tm_isdst)
  2936. # Same if utcoffset is None
  2937. class NOFS(DST):
  2938. def utcoffset(self, dt):
  2939. return None
  2940. d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
  2941. t = d.utctimetuple()
  2942. self.assertEqual(t[:-1], d.timetuple()[:-1])
  2943. self.assertEqual(0, t.tm_isdst)
  2944. # Check that bad tzinfo is detected
  2945. class BOFS(DST):
  2946. def utcoffset(self, dt):
  2947. return "EST"
  2948. d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
  2949. self.assertRaises(TypeError, d.utctimetuple)
  2950. # Check that utctimetuple() is the same as
  2951. # astimezone(utc).timetuple()
  2952. d = cls(2010, 11, 13, 14, 15, 16, 171819)
  2953. for tz in [timezone.min, timezone.utc, timezone.max]:
  2954. dtz = d.replace(tzinfo=tz)
  2955. self.assertEqual(dtz.utctimetuple()[:-1],
  2956. dtz.astimezone(timezone.utc).timetuple()[:-1])
  2957. # At the edges, UTC adjustment can produce years out-of-range
  2958. # for a datetime object. Ensure that an OverflowError is
  2959. # raised.
  2960. tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
  2961. # That goes back 1 minute less than a full day.
  2962. self.assertRaises(OverflowError, tiny.utctimetuple)
  2963. huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
  2964. # That goes forward 1 minute less than a full day.
  2965. self.assertRaises(OverflowError, huge.utctimetuple)
  2966. # More overflow cases
  2967. tiny = cls.min.replace(tzinfo=timezone(MINUTE))
  2968. self.assertRaises(OverflowError, tiny.utctimetuple)
  2969. huge = cls.max.replace(tzinfo=timezone(-MINUTE))
  2970. self.assertRaises(OverflowError, huge.utctimetuple)
  2971. def test_tzinfo_isoformat(self):
  2972. zero = FixedOffset(0, "+00:00")
  2973. plus = FixedOffset(220, "+03:40")
  2974. minus = FixedOffset(-231, "-03:51")
  2975. unknown = FixedOffset(None, "")
  2976. cls = self.theclass
  2977. datestr = '0001-02-03'
  2978. for ofs in None, zero, plus, minus, unknown:
  2979. for us in 0, 987001:
  2980. d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
  2981. timestr = '04:05:59' + (us and '.987001' or '')
  2982. ofsstr = ofs is not None and d.tzname() or ''
  2983. tailstr = timestr + ofsstr
  2984. iso = d.isoformat()
  2985. self.assertEqual(iso, datestr + 'T' + tailstr)
  2986. self.assertEqual(iso, d.isoformat('T'))
  2987. self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
  2988. self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
  2989. self.assertEqual(str(d), datestr + ' ' + tailstr)
  2990. def test_replace(self):
  2991. cls = self.theclass
  2992. z100 = FixedOffset(100, "+100")
  2993. zm200 = FixedOffset(timedelta(minutes=-200), "-200")
  2994. args = [1, 2, 3, 4, 5, 6, 7, z100]
  2995. base = cls(*args)
  2996. self.assertEqual(base, base.replace())
  2997. i = 0
  2998. for name, newval in (("year", 2),
  2999. ("month", 3),
  3000. ("day", 4),
  3001. ("hour", 5),
  3002. ("minute", 6),
  3003. ("second", 7),
  3004. ("microsecond", 8),
  3005. ("tzinfo", zm200)):
  3006. newargs = args[:]
  3007. newargs[i] = newval
  3008. expected = cls(*newargs)
  3009. got = base.replace(**{name: newval})
  3010. self.assertEqual(expected, got)
  3011. i += 1
  3012. # Ensure we can get rid of a tzinfo.
  3013. self.assertEqual(base.tzname(), "+100")
  3014. base2 = base.replace(tzinfo=None)
  3015. self.assertIsNone(base2.tzinfo)
  3016. self.assertIsNone(base2.tzname())
  3017. # Ensure we can add one.
  3018. base3 = base2.replace(tzinfo=z100)
  3019. self.assertEqual(base, base3)
  3020. self.assertIs(base.tzinfo, base3.tzinfo)
  3021. # Out of bounds.
  3022. base = cls(2000, 2, 29)
  3023. self.assertRaises(ValueError, base.replace, year=2001)
  3024. def test_more_astimezone(self):
  3025. # The inherited test_astimezone covered some trivial and error cases.
  3026. fnone = FixedOffset(None, "None")
  3027. f44m = FixedOffset(44, "44")
  3028. fm5h = FixedOffset(-timedelta(hours=5), "m300")
  3029. dt = self.theclass.now(tz=f44m)
  3030. self.assertIs(dt.tzinfo, f44m)
  3031. # Replacing with degenerate tzinfo raises an exception.
  3032. self.assertRaises(ValueError, dt.astimezone, fnone)
  3033. # Replacing with same tzinfo makes no change.
  3034. x = dt.astimezone(dt.tzinfo)
  3035. self.assertIs(x.tzinfo, f44m)
  3036. self.assertEqual(x.date(), dt.date())
  3037. self.assertEqual(x.time(), dt.time())
  3038. # Replacing with different tzinfo does adjust.
  3039. got = dt.astimezone(fm5h)
  3040. self.assertIs(got.tzinfo, fm5h)
  3041. self.assertEqual(got.utcoffset(), timedelta(hours=-5))
  3042. expected = dt - dt.utcoffset() # in effect, convert to UTC
  3043. expected += fm5h.utcoffset(dt) # and from there to local time
  3044. expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
  3045. self.assertEqual(got.date(), expected.date())
  3046. self.assertEqual(got.time(), expected.time())
  3047. self.assertEqual(got.timetz(), expected.timetz())
  3048. self.assertIs(got.tzinfo, expected.tzinfo)
  3049. self.assertEqual(got, expected)
  3050. @support.run_with_tz('UTC')
  3051. def test_astimezone_default_utc(self):
  3052. dt = self.theclass.now(timezone.utc)
  3053. self.assertEqual(dt.astimezone(None), dt)
  3054. self.assertEqual(dt.astimezone(), dt)
  3055. # Note that offset in TZ variable has the opposite sign to that
  3056. # produced by %z directive.
  3057. @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
  3058. def test_astimezone_default_eastern(self):
  3059. dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
  3060. local = dt.astimezone()
  3061. self.assertEqual(dt, local)
  3062. self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
  3063. dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
  3064. local = dt.astimezone()
  3065. self.assertEqual(dt, local)
  3066. self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
  3067. @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
  3068. def test_astimezone_default_near_fold(self):
  3069. # Issue #26616.
  3070. u = datetime(2015, 11, 1, 5, tzinfo=timezone.utc)
  3071. t = u.astimezone()
  3072. s = t.astimezone()
  3073. self.assertEqual(t.tzinfo, s.tzinfo)
  3074. def test_aware_subtract(self):
  3075. cls = self.theclass
  3076. # Ensure that utcoffset() is ignored when the operands have the
  3077. # same tzinfo member.
  3078. class OperandDependentOffset(tzinfo):
  3079. def utcoffset(self, t):
  3080. if t.minute < 10:
  3081. # d0 and d1 equal after adjustment
  3082. return timedelta(minutes=t.minute)
  3083. else:
  3084. # d2 off in the weeds
  3085. return timedelta(minutes=59)
  3086. base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
  3087. d0 = base.replace(minute=3)
  3088. d1 = base.replace(minute=9)
  3089. d2 = base.replace(minute=11)
  3090. for x in d0, d1, d2:
  3091. for y in d0, d1, d2:
  3092. got = x - y
  3093. expected = timedelta(minutes=x.minute - y.minute)
  3094. self.assertEqual(got, expected)
  3095. # OTOH, if the tzinfo members are distinct, utcoffsets aren't
  3096. # ignored.
  3097. base = cls(8, 9, 10, 11, 12, 13, 14)
  3098. d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
  3099. d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
  3100. d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
  3101. for x in d0, d1, d2:
  3102. for y in d0, d1, d2:
  3103. got = x - y
  3104. if (x is d0 or x is d1) and (y is d0 or y is d1):
  3105. expected = timedelta(0)
  3106. elif x is y is d2:
  3107. expected = timedelta(0)
  3108. elif x is d2:
  3109. expected = timedelta(minutes=(11-59)-0)
  3110. else:
  3111. assert y is d2
  3112. expected = timedelta(minutes=0-(11-59))
  3113. self.assertEqual(got, expected)
  3114. def test_mixed_compare(self):
  3115. t1 = datetime(1, 2, 3, 4, 5, 6, 7)
  3116. t2 = datetime(1, 2, 3, 4, 5, 6, 7)
  3117. self.assertEqual(t1, t2)
  3118. t2 = t2.replace(tzinfo=None)
  3119. self.assertEqual(t1, t2)
  3120. t2 = t2.replace(tzinfo=FixedOffset(None, ""))
  3121. self.assertEqual(t1, t2)
  3122. t2 = t2.replace(tzinfo=FixedOffset(0, ""))
  3123. self.assertNotEqual(t1, t2)
  3124. # In datetime w/ identical tzinfo objects, utcoffset is ignored.
  3125. class Varies(tzinfo):
  3126. def __init__(self):
  3127. self.offset = timedelta(minutes=22)
  3128. def utcoffset(self, t):
  3129. self.offset += timedelta(minutes=1)
  3130. return self.offset
  3131. v = Varies()
  3132. t1 = t2.replace(tzinfo=v)
  3133. t2 = t2.replace(tzinfo=v)
  3134. self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
  3135. self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
  3136. self.assertEqual(t1, t2)
  3137. # But if they're not identical, it isn't ignored.
  3138. t2 = t2.replace(tzinfo=Varies())
  3139. self.assertTrue(t1 < t2) # t1's offset counter still going up
  3140. def test_subclass_datetimetz(self):
  3141. class C(self.theclass):
  3142. theAnswer = 42
  3143. def __new__(cls, *args, **kws):
  3144. temp = kws.copy()
  3145. extra = temp.pop('extra')
  3146. result = self.theclass.__new__(cls, *args, **temp)
  3147. result.extra = extra
  3148. return result
  3149. def newmeth(self, start):
  3150. return start + self.hour + self.year
  3151. args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
  3152. dt1 = self.theclass(*args)
  3153. dt2 = C(*args, **{'extra': 7})
  3154. self.assertEqual(dt2.__class__, C)
  3155. self.assertEqual(dt2.theAnswer, 42)
  3156. self.assertEqual(dt2.extra, 7)
  3157. self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
  3158. self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
  3159. # Pain to set up DST-aware tzinfo classes.
  3160. def first_sunday_on_or_after(dt):
  3161. days_to_go = 6 - dt.weekday()
  3162. if days_to_go:
  3163. dt += timedelta(days_to_go)
  3164. return dt
  3165. ZERO = timedelta(0)
  3166. MINUTE = timedelta(minutes=1)
  3167. HOUR = timedelta(hours=1)
  3168. DAY = timedelta(days=1)
  3169. # In the US, DST starts at 2am (standard time) on the first Sunday in April.
  3170. DSTSTART = datetime(1, 4, 1, 2)
  3171. # and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
  3172. # which is the first Sunday on or after Oct 25. Because we view 1:MM as
  3173. # being standard time on that day, there is no spelling in local time of
  3174. # the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
  3175. DSTEND = datetime(1, 10, 25, 1)
  3176. class USTimeZone(tzinfo):
  3177. def __init__(self, hours, reprname, stdname, dstname):
  3178. self.stdoffset = timedelta(hours=hours)
  3179. self.reprname = reprname
  3180. self.stdname = stdname
  3181. self.dstname = dstname
  3182. def __repr__(self):
  3183. return self.reprname
  3184. def tzname(self, dt):
  3185. if self.dst(dt):
  3186. return self.dstname
  3187. else:
  3188. return self.stdname
  3189. def utcoffset(self, dt):
  3190. return self.stdoffset + self.dst(dt)
  3191. def dst(self, dt):
  3192. if dt is None or dt.tzinfo is None:
  3193. # An exception instead may be sensible here, in one or more of
  3194. # the cases.
  3195. return ZERO
  3196. assert dt.tzinfo is self
  3197. # Find first Sunday in April.
  3198. start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
  3199. assert start.weekday() == 6 and start.month == 4 and start.day <= 7
  3200. # Find last Sunday in October.
  3201. end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
  3202. assert end.weekday() == 6 and end.month == 10 and end.day >= 25
  3203. # Can't compare naive to aware objects, so strip the timezone from
  3204. # dt first.
  3205. if start <= dt.replace(tzinfo=None) < end:
  3206. return HOUR
  3207. else:
  3208. return ZERO
  3209. Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
  3210. Central = USTimeZone(-6, "Central", "CST", "CDT")
  3211. Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
  3212. Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
  3213. utc_real = FixedOffset(0, "UTC", 0)
  3214. # For better test coverage, we want another flavor of UTC that's west of
  3215. # the Eastern and Pacific timezones.
  3216. utc_fake = FixedOffset(-12*60, "UTCfake", 0)
  3217. class TestTimezoneConversions(unittest.TestCase):
  3218. # The DST switch times for 2002, in std time.
  3219. dston = datetime(2002, 4, 7, 2)
  3220. dstoff = datetime(2002, 10, 27, 1)
  3221. theclass = datetime
  3222. # Check a time that's inside DST.
  3223. def checkinside(self, dt, tz, utc, dston, dstoff):
  3224. self.assertEqual(dt.dst(), HOUR)
  3225. # Conversion to our own timezone is always an identity.
  3226. self.assertEqual(dt.astimezone(tz), dt)
  3227. asutc = dt.astimezone(utc)
  3228. there_and_back = asutc.astimezone(tz)
  3229. # Conversion to UTC and back isn't always an identity here,
  3230. # because there are redundant spellings (in local time) of
  3231. # UTC time when DST begins: the clock jumps from 1:59:59
  3232. # to 3:00:00, and a local time of 2:MM:SS doesn't really
  3233. # make sense then. The classes above treat 2:MM:SS as
  3234. # daylight time then (it's "after 2am"), really an alias
  3235. # for 1:MM:SS standard time. The latter form is what
  3236. # conversion back from UTC produces.
  3237. if dt.date() == dston.date() and dt.hour == 2:
  3238. # We're in the redundant hour, and coming back from
  3239. # UTC gives the 1:MM:SS standard-time spelling.
  3240. self.assertEqual(there_and_back + HOUR, dt)
  3241. # Although during was considered to be in daylight
  3242. # time, there_and_back is not.
  3243. self.assertEqual(there_and_back.dst(), ZERO)
  3244. # They're the same times in UTC.
  3245. self.assertEqual(there_and_back.astimezone(utc),
  3246. dt.astimezone(utc))
  3247. else:
  3248. # We're not in the redundant hour.
  3249. self.assertEqual(dt, there_and_back)
  3250. # Because we have a redundant spelling when DST begins, there is
  3251. # (unfortunately) an hour when DST ends that can't be spelled at all in
  3252. # local time. When DST ends, the clock jumps from 1:59 back to 1:00
  3253. # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
  3254. # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
  3255. # daylight time. The hour 1:MM daylight == 0:MM standard can't be
  3256. # expressed in local time. Nevertheless, we want conversion back
  3257. # from UTC to mimic the local clock's "repeat an hour" behavior.
  3258. nexthour_utc = asutc + HOUR
  3259. nexthour_tz = nexthour_utc.astimezone(tz)
  3260. if dt.date() == dstoff.date() and dt.hour == 0:
  3261. # We're in the hour before the last DST hour. The last DST hour
  3262. # is ineffable. We want the conversion back to repeat 1:MM.
  3263. self.assertEqual(nexthour_tz, dt.replace(hour=1))
  3264. nexthour_utc += HOUR
  3265. nexthour_tz = nexthour_utc.astimezone(tz)
  3266. self.assertEqual(nexthour_tz, dt.replace(hour=1))
  3267. else:
  3268. self.assertEqual(nexthour_tz - dt, HOUR)
  3269. # Check a time that's outside DST.
  3270. def checkoutside(self, dt, tz, utc):
  3271. self.assertEqual(dt.dst(), ZERO)
  3272. # Conversion to our own timezone is always an identity.
  3273. self.assertEqual(dt.astimezone(tz), dt)
  3274. # Converting to UTC and back is an identity too.
  3275. asutc = dt.astimezone(utc)
  3276. there_and_back = asutc.astimezone(tz)
  3277. self.assertEqual(dt, there_and_back)
  3278. def convert_between_tz_and_utc(self, tz, utc):
  3279. dston = self.dston.replace(tzinfo=tz)
  3280. # Because 1:MM on the day DST ends is taken as being standard time,
  3281. # there is no spelling in tz for the last hour of daylight time.
  3282. # For purposes of the test, the last hour of DST is 0:MM, which is
  3283. # taken as being daylight time (and 1:MM is taken as being standard
  3284. # time).
  3285. dstoff = self.dstoff.replace(tzinfo=tz)
  3286. for delta in (timedelta(weeks=13),
  3287. DAY,
  3288. HOUR,
  3289. timedelta(minutes=1),
  3290. timedelta(microseconds=1)):
  3291. self.checkinside(dston, tz, utc, dston, dstoff)
  3292. for during in dston + delta, dstoff - delta:
  3293. self.checkinside(during, tz, utc, dston, dstoff)
  3294. self.checkoutside(dstoff, tz, utc)
  3295. for outside in dston - delta, dstoff + delta:
  3296. self.checkoutside(outside, tz, utc)
  3297. def test_easy(self):
  3298. # Despite the name of this test, the endcases are excruciating.
  3299. self.convert_between_tz_and_utc(Eastern, utc_real)
  3300. self.convert_between_tz_and_utc(Pacific, utc_real)
  3301. self.convert_between_tz_and_utc(Eastern, utc_fake)
  3302. self.convert_between_tz_and_utc(Pacific, utc_fake)
  3303. # The next is really dancing near the edge. It works because
  3304. # Pacific and Eastern are far enough apart that their "problem
  3305. # hours" don't overlap.
  3306. self.convert_between_tz_and_utc(Eastern, Pacific)
  3307. self.convert_between_tz_and_utc(Pacific, Eastern)
  3308. # OTOH, these fail! Don't enable them. The difficulty is that
  3309. # the edge case tests assume that every hour is representable in
  3310. # the "utc" class. This is always true for a fixed-offset tzinfo
  3311. # class (lke utc_real and utc_fake), but not for Eastern or Central.
  3312. # For these adjacent DST-aware time zones, the range of time offsets
  3313. # tested ends up creating hours in the one that aren't representable
  3314. # in the other. For the same reason, we would see failures in the
  3315. # Eastern vs Pacific tests too if we added 3*HOUR to the list of
  3316. # offset deltas in convert_between_tz_and_utc().
  3317. #
  3318. # self.convert_between_tz_and_utc(Eastern, Central) # can't work
  3319. # self.convert_between_tz_and_utc(Central, Eastern) # can't work
  3320. def test_tricky(self):
  3321. # 22:00 on day before daylight starts.
  3322. fourback = self.dston - timedelta(hours=4)
  3323. ninewest = FixedOffset(-9*60, "-0900", 0)
  3324. fourback = fourback.replace(tzinfo=ninewest)
  3325. # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
  3326. # 2", we should get the 3 spelling.
  3327. # If we plug 22:00 the day before into Eastern, it "looks like std
  3328. # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
  3329. # to 22:00 lands on 2:00, which makes no sense in local time (the
  3330. # local clock jumps from 1 to 3). The point here is to make sure we
  3331. # get the 3 spelling.
  3332. expected = self.dston.replace(hour=3)
  3333. got = fourback.astimezone(Eastern).replace(tzinfo=None)
  3334. self.assertEqual(expected, got)
  3335. # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
  3336. # case we want the 1:00 spelling.
  3337. sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
  3338. # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
  3339. # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
  3340. # spelling.
  3341. expected = self.dston.replace(hour=1)
  3342. got = sixutc.astimezone(Eastern).replace(tzinfo=None)
  3343. self.assertEqual(expected, got)
  3344. # Now on the day DST ends, we want "repeat an hour" behavior.
  3345. # UTC 4:MM 5:MM 6:MM 7:MM checking these
  3346. # EST 23:MM 0:MM 1:MM 2:MM
  3347. # EDT 0:MM 1:MM 2:MM 3:MM
  3348. # wall 0:MM 1:MM 1:MM 2:MM against these
  3349. for utc in utc_real, utc_fake:
  3350. for tz in Eastern, Pacific:
  3351. first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
  3352. # Convert that to UTC.
  3353. first_std_hour -= tz.utcoffset(None)
  3354. # Adjust for possibly fake UTC.
  3355. asutc = first_std_hour + utc.utcoffset(None)
  3356. # First UTC hour to convert; this is 4:00 when utc=utc_real &
  3357. # tz=Eastern.
  3358. asutcbase = asutc.replace(tzinfo=utc)
  3359. for tzhour in (0, 1, 1, 2):
  3360. expectedbase = self.dstoff.replace(hour=tzhour)
  3361. for minute in 0, 30, 59:
  3362. expected = expectedbase.replace(minute=minute)
  3363. asutc = asutcbase.replace(minute=minute)
  3364. astz = asutc.astimezone(tz)
  3365. self.assertEqual(astz.replace(tzinfo=None), expected)
  3366. asutcbase += HOUR
  3367. def test_bogus_dst(self):
  3368. class ok(tzinfo):
  3369. def utcoffset(self, dt): return HOUR
  3370. def dst(self, dt): return HOUR
  3371. now = self.theclass.now().replace(tzinfo=utc_real)
  3372. # Doesn't blow up.
  3373. now.astimezone(ok())
  3374. # Does blow up.
  3375. class notok(ok):
  3376. def dst(self, dt): return None
  3377. self.assertRaises(ValueError, now.astimezone, notok())
  3378. # Sometimes blow up. In the following, tzinfo.dst()
  3379. # implementation may return None or not None depending on
  3380. # whether DST is assumed to be in effect. In this situation,
  3381. # a ValueError should be raised by astimezone().
  3382. class tricky_notok(ok):
  3383. def dst(self, dt):
  3384. if dt.year == 2000:
  3385. return None
  3386. else:
  3387. return 10*HOUR
  3388. dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
  3389. self.assertRaises(ValueError, dt.astimezone, tricky_notok())
  3390. def test_fromutc(self):
  3391. self.assertRaises(TypeError, Eastern.fromutc) # not enough args
  3392. now = datetime.utcnow().replace(tzinfo=utc_real)
  3393. self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
  3394. now = now.replace(tzinfo=Eastern) # insert correct tzinfo
  3395. enow = Eastern.fromutc(now) # doesn't blow up
  3396. self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
  3397. self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
  3398. self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
  3399. # Always converts UTC to standard time.
  3400. class FauxUSTimeZone(USTimeZone):
  3401. def fromutc(self, dt):
  3402. return dt + self.stdoffset
  3403. FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
  3404. # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
  3405. # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
  3406. # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
  3407. # Check around DST start.
  3408. start = self.dston.replace(hour=4, tzinfo=Eastern)
  3409. fstart = start.replace(tzinfo=FEastern)
  3410. for wall in 23, 0, 1, 3, 4, 5:
  3411. expected = start.replace(hour=wall)
  3412. if wall == 23:
  3413. expected -= timedelta(days=1)
  3414. got = Eastern.fromutc(start)
  3415. self.assertEqual(expected, got)
  3416. expected = fstart + FEastern.stdoffset
  3417. got = FEastern.fromutc(fstart)
  3418. self.assertEqual(expected, got)
  3419. # Ensure astimezone() calls fromutc() too.
  3420. got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
  3421. self.assertEqual(expected, got)
  3422. start += HOUR
  3423. fstart += HOUR
  3424. # Check around DST end.
  3425. start = self.dstoff.replace(hour=4, tzinfo=Eastern)
  3426. fstart = start.replace(tzinfo=FEastern)
  3427. for wall in 0, 1, 1, 2, 3, 4:
  3428. expected = start.replace(hour=wall)
  3429. got = Eastern.fromutc(start)
  3430. self.assertEqual(expected, got)
  3431. expected = fstart + FEastern.stdoffset
  3432. got = FEastern.fromutc(fstart)
  3433. self.assertEqual(expected, got)
  3434. # Ensure astimezone() calls fromutc() too.
  3435. got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
  3436. self.assertEqual(expected, got)
  3437. start += HOUR
  3438. fstart += HOUR
  3439. #############################################################################
  3440. # oddballs
  3441. class Oddballs(unittest.TestCase):
  3442. def test_bug_1028306(self):
  3443. # Trying to compare a date to a datetime should act like a mixed-
  3444. # type comparison, despite that datetime is a subclass of date.
  3445. as_date = date.today()
  3446. as_datetime = datetime.combine(as_date, time())
  3447. self.assertTrue(as_date != as_datetime)
  3448. self.assertTrue(as_datetime != as_date)
  3449. self.assertFalse(as_date == as_datetime)
  3450. self.assertFalse(as_datetime == as_date)
  3451. self.assertRaises(TypeError, lambda: as_date < as_datetime)
  3452. self.assertRaises(TypeError, lambda: as_datetime < as_date)
  3453. self.assertRaises(TypeError, lambda: as_date <= as_datetime)
  3454. self.assertRaises(TypeError, lambda: as_datetime <= as_date)
  3455. self.assertRaises(TypeError, lambda: as_date > as_datetime)
  3456. self.assertRaises(TypeError, lambda: as_datetime > as_date)
  3457. self.assertRaises(TypeError, lambda: as_date >= as_datetime)
  3458. self.assertRaises(TypeError, lambda: as_datetime >= as_date)
  3459. # Nevertheless, comparison should work with the base-class (date)
  3460. # projection if use of a date method is forced.
  3461. self.assertEqual(as_date.__eq__(as_datetime), True)
  3462. different_day = (as_date.day + 1) % 20 + 1
  3463. as_different = as_datetime.replace(day= different_day)
  3464. self.assertEqual(as_date.__eq__(as_different), False)
  3465. # And date should compare with other subclasses of date. If a
  3466. # subclass wants to stop this, it's up to the subclass to do so.
  3467. date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
  3468. self.assertEqual(as_date, date_sc)
  3469. self.assertEqual(date_sc, as_date)
  3470. # Ditto for datetimes.
  3471. datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
  3472. as_date.day, 0, 0, 0)
  3473. self.assertEqual(as_datetime, datetime_sc)
  3474. self.assertEqual(datetime_sc, as_datetime)
  3475. def test_extra_attributes(self):
  3476. for x in [date.today(),
  3477. time(),
  3478. datetime.utcnow(),
  3479. timedelta(),
  3480. tzinfo(),
  3481. timezone(timedelta())]:
  3482. with self.assertRaises(AttributeError):
  3483. x.abc = 1
  3484. def test_check_arg_types(self):
  3485. class Number:
  3486. def __init__(self, value):
  3487. self.value = value
  3488. def __int__(self):
  3489. return self.value
  3490. for xx in [decimal.Decimal(10),
  3491. decimal.Decimal('10.9'),
  3492. Number(10)]:
  3493. self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
  3494. datetime(xx, xx, xx, xx, xx, xx, xx))
  3495. with self.assertRaisesRegex(TypeError, '^an integer is required '
  3496. r'\(got type str\)$'):
  3497. datetime(10, 10, '10')
  3498. f10 = Number(10.9)
  3499. with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
  3500. r'\(type float\)$'):
  3501. datetime(10, 10, f10)
  3502. class Float(float):
  3503. pass
  3504. s10 = Float(10.9)
  3505. with self.assertRaisesRegex(TypeError, '^integer argument expected, '
  3506. 'got float$'):
  3507. datetime(10, 10, s10)
  3508. with self.assertRaises(TypeError):
  3509. datetime(10., 10, 10)
  3510. with self.assertRaises(TypeError):
  3511. datetime(10, 10., 10)
  3512. with self.assertRaises(TypeError):
  3513. datetime(10, 10, 10.)
  3514. with self.assertRaises(TypeError):
  3515. datetime(10, 10, 10, 10.)
  3516. with self.assertRaises(TypeError):
  3517. datetime(10, 10, 10, 10, 10.)
  3518. with self.assertRaises(TypeError):
  3519. datetime(10, 10, 10, 10, 10, 10.)
  3520. with self.assertRaises(TypeError):
  3521. datetime(10, 10, 10, 10, 10, 10, 10.)
  3522. #############################################################################
  3523. # Local Time Disambiguation
  3524. # An experimental reimplementation of fromutc that respects the "fold" flag.
  3525. class tzinfo2(tzinfo):
  3526. def fromutc(self, dt):
  3527. "datetime in UTC -> datetime in local time."
  3528. if not isinstance(dt, datetime):
  3529. raise TypeError("fromutc() requires a datetime argument")
  3530. if dt.tzinfo is not self:
  3531. raise ValueError("dt.tzinfo is not self")
  3532. # Returned value satisfies
  3533. # dt + ldt.utcoffset() = ldt
  3534. off0 = dt.replace(fold=0).utcoffset()
  3535. off1 = dt.replace(fold=1).utcoffset()
  3536. if off0 is None or off1 is None or dt.dst() is None:
  3537. raise ValueError
  3538. if off0 == off1:
  3539. ldt = dt + off0
  3540. off1 = ldt.utcoffset()
  3541. if off0 == off1:
  3542. return ldt
  3543. # Now, we discovered both possible offsets, so
  3544. # we can just try four possible solutions:
  3545. for off in [off0, off1]:
  3546. ldt = dt + off
  3547. if ldt.utcoffset() == off:
  3548. return ldt
  3549. ldt = ldt.replace(fold=1)
  3550. if ldt.utcoffset() == off:
  3551. return ldt
  3552. raise ValueError("No suitable local time found")
  3553. # Reimplementing simplified US timezones to respect the "fold" flag:
  3554. class USTimeZone2(tzinfo2):
  3555. def __init__(self, hours, reprname, stdname, dstname):
  3556. self.stdoffset = timedelta(hours=hours)
  3557. self.reprname = reprname
  3558. self.stdname = stdname
  3559. self.dstname = dstname
  3560. def __repr__(self):
  3561. return self.reprname
  3562. def tzname(self, dt):
  3563. if self.dst(dt):
  3564. return self.dstname
  3565. else:
  3566. return self.stdname
  3567. def utcoffset(self, dt):
  3568. return self.stdoffset + self.dst(dt)
  3569. def dst(self, dt):
  3570. if dt is None or dt.tzinfo is None:
  3571. # An exception instead may be sensible here, in one or more of
  3572. # the cases.
  3573. return ZERO
  3574. assert dt.tzinfo is self
  3575. # Find first Sunday in April.
  3576. start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
  3577. assert start.weekday() == 6 and start.month == 4 and start.day <= 7
  3578. # Find last Sunday in October.
  3579. end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
  3580. assert end.weekday() == 6 and end.month == 10 and end.day >= 25
  3581. # Can't compare naive to aware objects, so strip the timezone from
  3582. # dt first.
  3583. dt = dt.replace(tzinfo=None)
  3584. if start + HOUR <= dt < end:
  3585. # DST is in effect.
  3586. return HOUR
  3587. elif end <= dt < end + HOUR:
  3588. # Fold (an ambiguous hour): use dt.fold to disambiguate.
  3589. return ZERO if dt.fold else HOUR
  3590. elif start <= dt < start + HOUR:
  3591. # Gap (a non-existent hour): reverse the fold rule.
  3592. return HOUR if dt.fold else ZERO
  3593. else:
  3594. # DST is off.
  3595. return ZERO
  3596. Eastern2 = USTimeZone2(-5, "Eastern2", "EST", "EDT")
  3597. Central2 = USTimeZone2(-6, "Central2", "CST", "CDT")
  3598. Mountain2 = USTimeZone2(-7, "Mountain2", "MST", "MDT")
  3599. Pacific2 = USTimeZone2(-8, "Pacific2", "PST", "PDT")
  3600. # Europe_Vilnius_1941 tzinfo implementation reproduces the following
  3601. # 1941 transition from Olson's tzdist:
  3602. #
  3603. # Zone NAME GMTOFF RULES FORMAT [UNTIL]
  3604. # ZoneEurope/Vilnius 1:00 - CET 1940 Aug 3
  3605. # 3:00 - MSK 1941 Jun 24
  3606. # 1:00 C-Eur CE%sT 1944 Aug
  3607. #
  3608. # $ zdump -v Europe/Vilnius | grep 1941
  3609. # Europe/Vilnius Mon Jun 23 20:59:59 1941 UTC = Mon Jun 23 23:59:59 1941 MSK isdst=0 gmtoff=10800
  3610. # Europe/Vilnius Mon Jun 23 21:00:00 1941 UTC = Mon Jun 23 23:00:00 1941 CEST isdst=1 gmtoff=7200
  3611. class Europe_Vilnius_1941(tzinfo):
  3612. def _utc_fold(self):
  3613. return [datetime(1941, 6, 23, 21, tzinfo=self), # Mon Jun 23 21:00:00 1941 UTC
  3614. datetime(1941, 6, 23, 22, tzinfo=self)] # Mon Jun 23 22:00:00 1941 UTC
  3615. def _loc_fold(self):
  3616. return [datetime(1941, 6, 23, 23, tzinfo=self), # Mon Jun 23 23:00:00 1941 MSK / CEST
  3617. datetime(1941, 6, 24, 0, tzinfo=self)] # Mon Jun 24 00:00:00 1941 CEST
  3618. def utcoffset(self, dt):
  3619. fold_start, fold_stop = self._loc_fold()
  3620. if dt < fold_start:
  3621. return 3 * HOUR
  3622. if dt < fold_stop:
  3623. return (2 if dt.fold else 3) * HOUR
  3624. # if dt >= fold_stop
  3625. return 2 * HOUR
  3626. def dst(self, dt):
  3627. fold_start, fold_stop = self._loc_fold()
  3628. if dt < fold_start:
  3629. return 0 * HOUR
  3630. if dt < fold_stop:
  3631. return (1 if dt.fold else 0) * HOUR
  3632. # if dt >= fold_stop
  3633. return 1 * HOUR
  3634. def tzname(self, dt):
  3635. fold_start, fold_stop = self._loc_fold()
  3636. if dt < fold_start:
  3637. return 'MSK'
  3638. if dt < fold_stop:
  3639. return ('MSK', 'CEST')[dt.fold]
  3640. # if dt >= fold_stop
  3641. return 'CEST'
  3642. def fromutc(self, dt):
  3643. assert dt.fold == 0
  3644. assert dt.tzinfo is self
  3645. if dt.year != 1941:
  3646. raise NotImplementedError
  3647. fold_start, fold_stop = self._utc_fold()
  3648. if dt < fold_start:
  3649. return dt + 3 * HOUR
  3650. if dt < fold_stop:
  3651. return (dt + 2 * HOUR).replace(fold=1)
  3652. # if dt >= fold_stop
  3653. return dt + 2 * HOUR
  3654. class TestLocalTimeDisambiguation(unittest.TestCase):
  3655. def test_vilnius_1941_fromutc(self):
  3656. Vilnius = Europe_Vilnius_1941()
  3657. gdt = datetime(1941, 6, 23, 20, 59, 59, tzinfo=timezone.utc)
  3658. ldt = gdt.astimezone(Vilnius)
  3659. self.assertEqual(ldt.strftime("%c %Z%z"),
  3660. 'Mon Jun 23 23:59:59 1941 MSK+0300')
  3661. self.assertEqual(ldt.fold, 0)
  3662. self.assertFalse(ldt.dst())
  3663. gdt = datetime(1941, 6, 23, 21, tzinfo=timezone.utc)
  3664. ldt = gdt.astimezone(Vilnius)
  3665. self.assertEqual(ldt.strftime("%c %Z%z"),
  3666. 'Mon Jun 23 23:00:00 1941 CEST+0200')
  3667. self.assertEqual(ldt.fold, 1)
  3668. self.assertTrue(ldt.dst())
  3669. gdt = datetime(1941, 6, 23, 22, tzinfo=timezone.utc)
  3670. ldt = gdt.astimezone(Vilnius)
  3671. self.assertEqual(ldt.strftime("%c %Z%z"),
  3672. 'Tue Jun 24 00:00:00 1941 CEST+0200')
  3673. self.assertEqual(ldt.fold, 0)
  3674. self.assertTrue(ldt.dst())
  3675. def test_vilnius_1941_toutc(self):
  3676. Vilnius = Europe_Vilnius_1941()
  3677. ldt = datetime(1941, 6, 23, 22, 59, 59, tzinfo=Vilnius)
  3678. gdt = ldt.astimezone(timezone.utc)
  3679. self.assertEqual(gdt.strftime("%c %Z"),
  3680. 'Mon Jun 23 19:59:59 1941 UTC')
  3681. ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius)
  3682. gdt = ldt.astimezone(timezone.utc)
  3683. self.assertEqual(gdt.strftime("%c %Z"),
  3684. 'Mon Jun 23 20:59:59 1941 UTC')
  3685. ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius, fold=1)
  3686. gdt = ldt.astimezone(timezone.utc)
  3687. self.assertEqual(gdt.strftime("%c %Z"),
  3688. 'Mon Jun 23 21:59:59 1941 UTC')
  3689. ldt = datetime(1941, 6, 24, 0, tzinfo=Vilnius)
  3690. gdt = ldt.astimezone(timezone.utc)
  3691. self.assertEqual(gdt.strftime("%c %Z"),
  3692. 'Mon Jun 23 22:00:00 1941 UTC')
  3693. def test_constructors(self):
  3694. t = time(0, fold=1)
  3695. dt = datetime(1, 1, 1, fold=1)
  3696. self.assertEqual(t.fold, 1)
  3697. self.assertEqual(dt.fold, 1)
  3698. with self.assertRaises(TypeError):
  3699. time(0, 0, 0, 0, None, 0)
  3700. def test_member(self):
  3701. dt = datetime(1, 1, 1, fold=1)
  3702. t = dt.time()
  3703. self.assertEqual(t.fold, 1)
  3704. t = dt.timetz()
  3705. self.assertEqual(t.fold, 1)
  3706. def test_replace(self):
  3707. t = time(0)
  3708. dt = datetime(1, 1, 1)
  3709. self.assertEqual(t.replace(fold=1).fold, 1)
  3710. self.assertEqual(dt.replace(fold=1).fold, 1)
  3711. self.assertEqual(t.replace(fold=0).fold, 0)
  3712. self.assertEqual(dt.replace(fold=0).fold, 0)
  3713. # Check that replacement of other fields does not change "fold".
  3714. t = t.replace(fold=1, tzinfo=Eastern)
  3715. dt = dt.replace(fold=1, tzinfo=Eastern)
  3716. self.assertEqual(t.replace(tzinfo=None).fold, 1)
  3717. self.assertEqual(dt.replace(tzinfo=None).fold, 1)
  3718. # Out of bounds.
  3719. with self.assertRaises(ValueError):
  3720. t.replace(fold=2)
  3721. with self.assertRaises(ValueError):
  3722. dt.replace(fold=2)
  3723. # Check that fold is a keyword-only argument
  3724. with self.assertRaises(TypeError):
  3725. t.replace(1, 1, 1, None, 1)
  3726. with self.assertRaises(TypeError):
  3727. dt.replace(1, 1, 1, 1, 1, 1, 1, None, 1)
  3728. def test_comparison(self):
  3729. t = time(0)
  3730. dt = datetime(1, 1, 1)
  3731. self.assertEqual(t, t.replace(fold=1))
  3732. self.assertEqual(dt, dt.replace(fold=1))
  3733. def test_hash(self):
  3734. t = time(0)
  3735. dt = datetime(1, 1, 1)
  3736. self.assertEqual(hash(t), hash(t.replace(fold=1)))
  3737. self.assertEqual(hash(dt), hash(dt.replace(fold=1)))
  3738. @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
  3739. def test_fromtimestamp(self):
  3740. s = 1414906200
  3741. dt0 = datetime.fromtimestamp(s)
  3742. dt1 = datetime.fromtimestamp(s + 3600)
  3743. self.assertEqual(dt0.fold, 0)
  3744. self.assertEqual(dt1.fold, 1)
  3745. @support.run_with_tz('Australia/Lord_Howe')
  3746. def test_fromtimestamp_lord_howe(self):
  3747. tm = _time.localtime(1.4e9)
  3748. if _time.strftime('%Z%z', tm) != 'LHST+1030':
  3749. self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
  3750. # $ TZ=Australia/Lord_Howe date -r 1428158700
  3751. # Sun Apr 5 01:45:00 LHDT 2015
  3752. # $ TZ=Australia/Lord_Howe date -r 1428160500
  3753. # Sun Apr 5 01:45:00 LHST 2015
  3754. s = 1428158700
  3755. t0 = datetime.fromtimestamp(s)
  3756. t1 = datetime.fromtimestamp(s + 1800)
  3757. self.assertEqual(t0, t1)
  3758. self.assertEqual(t0.fold, 0)
  3759. self.assertEqual(t1.fold, 1)
  3760. @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
  3761. def test_timestamp(self):
  3762. dt0 = datetime(2014, 11, 2, 1, 30)
  3763. dt1 = dt0.replace(fold=1)
  3764. self.assertEqual(dt0.timestamp() + 3600,
  3765. dt1.timestamp())
  3766. @support.run_with_tz('Australia/Lord_Howe')
  3767. def test_timestamp_lord_howe(self):
  3768. tm = _time.localtime(1.4e9)
  3769. if _time.strftime('%Z%z', tm) != 'LHST+1030':
  3770. self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
  3771. t = datetime(2015, 4, 5, 1, 45)
  3772. s0 = t.replace(fold=0).timestamp()
  3773. s1 = t.replace(fold=1).timestamp()
  3774. self.assertEqual(s0 + 1800, s1)
  3775. @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
  3776. def test_astimezone(self):
  3777. dt0 = datetime(2014, 11, 2, 1, 30)
  3778. dt1 = dt0.replace(fold=1)
  3779. # Convert both naive instances to aware.
  3780. adt0 = dt0.astimezone()
  3781. adt1 = dt1.astimezone()
  3782. # Check that the first instance in DST zone and the second in STD
  3783. self.assertEqual(adt0.tzname(), 'EDT')
  3784. self.assertEqual(adt1.tzname(), 'EST')
  3785. self.assertEqual(adt0 + HOUR, adt1)
  3786. # Aware instances with fixed offset tzinfo's always have fold=0
  3787. self.assertEqual(adt0.fold, 0)
  3788. self.assertEqual(adt1.fold, 0)
  3789. def test_pickle_fold(self):
  3790. t = time(fold=1)
  3791. dt = datetime(1, 1, 1, fold=1)
  3792. for pickler, unpickler, proto in pickle_choices:
  3793. for x in [t, dt]:
  3794. s = pickler.dumps(x, proto)
  3795. y = unpickler.loads(s)
  3796. self.assertEqual(x, y)
  3797. self.assertEqual((0 if proto < 4 else x.fold), y.fold)
  3798. def test_repr(self):
  3799. t = time(fold=1)
  3800. dt = datetime(1, 1, 1, fold=1)
  3801. self.assertEqual(repr(t), 'datetime.time(0, 0, fold=1)')
  3802. self.assertEqual(repr(dt),
  3803. 'datetime.datetime(1, 1, 1, 0, 0, fold=1)')
  3804. def test_dst(self):
  3805. # Let's first establish that things work in regular times.
  3806. dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
  3807. dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
  3808. self.assertEqual(dt_summer.dst(), HOUR)
  3809. self.assertEqual(dt_winter.dst(), ZERO)
  3810. # The disambiguation flag is ignored
  3811. self.assertEqual(dt_summer.replace(fold=1).dst(), HOUR)
  3812. self.assertEqual(dt_winter.replace(fold=1).dst(), ZERO)
  3813. # Pick local time in the fold.
  3814. for minute in [0, 30, 59]:
  3815. dt = datetime(2002, 10, 27, 1, minute, tzinfo=Eastern2)
  3816. # With fold=0 (the default) it is in DST.
  3817. self.assertEqual(dt.dst(), HOUR)
  3818. # With fold=1 it is in STD.
  3819. self.assertEqual(dt.replace(fold=1).dst(), ZERO)
  3820. # Pick local time in the gap.
  3821. for minute in [0, 30, 59]:
  3822. dt = datetime(2002, 4, 7, 2, minute, tzinfo=Eastern2)
  3823. # With fold=0 (the default) it is in STD.
  3824. self.assertEqual(dt.dst(), ZERO)
  3825. # With fold=1 it is in DST.
  3826. self.assertEqual(dt.replace(fold=1).dst(), HOUR)
  3827. def test_utcoffset(self):
  3828. # Let's first establish that things work in regular times.
  3829. dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
  3830. dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
  3831. self.assertEqual(dt_summer.utcoffset(), -4 * HOUR)
  3832. self.assertEqual(dt_winter.utcoffset(), -5 * HOUR)
  3833. # The disambiguation flag is ignored
  3834. self.assertEqual(dt_summer.replace(fold=1).utcoffset(), -4 * HOUR)
  3835. self.assertEqual(dt_winter.replace(fold=1).utcoffset(), -5 * HOUR)
  3836. def test_fromutc(self):
  3837. # Let's first establish that things work in regular times.
  3838. u_summer = datetime(2002, 10, 27, 6, tzinfo=Eastern2) - timedelta.resolution
  3839. u_winter = datetime(2002, 10, 27, 7, tzinfo=Eastern2)
  3840. t_summer = Eastern2.fromutc(u_summer)
  3841. t_winter = Eastern2.fromutc(u_winter)
  3842. self.assertEqual(t_summer, u_summer - 4 * HOUR)
  3843. self.assertEqual(t_winter, u_winter - 5 * HOUR)
  3844. self.assertEqual(t_summer.fold, 0)
  3845. self.assertEqual(t_winter.fold, 0)
  3846. # What happens in the fall-back fold?
  3847. u = datetime(2002, 10, 27, 5, 30, tzinfo=Eastern2)
  3848. t0 = Eastern2.fromutc(u)
  3849. u += HOUR
  3850. t1 = Eastern2.fromutc(u)
  3851. self.assertEqual(t0, t1)
  3852. self.assertEqual(t0.fold, 0)
  3853. self.assertEqual(t1.fold, 1)
  3854. # The tricky part is when u is in the local fold:
  3855. u = datetime(2002, 10, 27, 1, 30, tzinfo=Eastern2)
  3856. t = Eastern2.fromutc(u)
  3857. self.assertEqual((t.day, t.hour), (26, 21))
  3858. # .. or gets into the local fold after a standard time adjustment
  3859. u = datetime(2002, 10, 27, 6, 30, tzinfo=Eastern2)
  3860. t = Eastern2.fromutc(u)
  3861. self.assertEqual((t.day, t.hour), (27, 1))
  3862. # What happens in the spring-forward gap?
  3863. u = datetime(2002, 4, 7, 2, 0, tzinfo=Eastern2)
  3864. t = Eastern2.fromutc(u)
  3865. self.assertEqual((t.day, t.hour), (6, 21))
  3866. def test_mixed_compare_regular(self):
  3867. t = datetime(2000, 1, 1, tzinfo=Eastern2)
  3868. self.assertEqual(t, t.astimezone(timezone.utc))
  3869. t = datetime(2000, 6, 1, tzinfo=Eastern2)
  3870. self.assertEqual(t, t.astimezone(timezone.utc))
  3871. def test_mixed_compare_fold(self):
  3872. t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
  3873. t_fold_utc = t_fold.astimezone(timezone.utc)
  3874. self.assertNotEqual(t_fold, t_fold_utc)
  3875. def test_mixed_compare_gap(self):
  3876. t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
  3877. t_gap_utc = t_gap.astimezone(timezone.utc)
  3878. self.assertNotEqual(t_gap, t_gap_utc)
  3879. def test_hash_aware(self):
  3880. t = datetime(2000, 1, 1, tzinfo=Eastern2)
  3881. self.assertEqual(hash(t), hash(t.replace(fold=1)))
  3882. t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
  3883. t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
  3884. self.assertEqual(hash(t_fold), hash(t_fold.replace(fold=1)))
  3885. self.assertEqual(hash(t_gap), hash(t_gap.replace(fold=1)))
  3886. SEC = timedelta(0, 1)
  3887. def pairs(iterable):
  3888. a, b = itertools.tee(iterable)
  3889. next(b, None)
  3890. return zip(a, b)
  3891. class ZoneInfo(tzinfo):
  3892. zoneroot = '/usr/share/zoneinfo'
  3893. def __init__(self, ut, ti):
  3894. """
  3895. :param ut: array
  3896. Array of transition point timestamps
  3897. :param ti: list
  3898. A list of (offset, isdst, abbr) tuples
  3899. :return: None
  3900. """
  3901. self.ut = ut
  3902. self.ti = ti
  3903. self.lt = self.invert(ut, ti)
  3904. @staticmethod
  3905. def invert(ut, ti):
  3906. lt = (array('q', ut), array('q', ut))
  3907. if ut:
  3908. offset = ti[0][0] // SEC
  3909. lt[0][0] += offset
  3910. lt[1][0] += offset
  3911. for i in range(1, len(ut)):
  3912. lt[0][i] += ti[i-1][0] // SEC
  3913. lt[1][i] += ti[i][0] // SEC
  3914. return lt
  3915. @classmethod
  3916. def fromfile(cls, fileobj):
  3917. if fileobj.read(4).decode() != "TZif":
  3918. raise ValueError("not a zoneinfo file")
  3919. fileobj.seek(32)
  3920. counts = array('i')
  3921. counts.fromfile(fileobj, 3)
  3922. if sys.byteorder != 'big':
  3923. counts.byteswap()
  3924. ut = array('i')
  3925. ut.fromfile(fileobj, counts[0])
  3926. if sys.byteorder != 'big':
  3927. ut.byteswap()
  3928. type_indices = array('B')
  3929. type_indices.fromfile(fileobj, counts[0])
  3930. ttis = []
  3931. for i in range(counts[1]):
  3932. ttis.append(struct.unpack(">lbb", fileobj.read(6)))
  3933. abbrs = fileobj.read(counts[2])
  3934. # Convert ttis
  3935. for i, (gmtoff, isdst, abbrind) in enumerate(ttis):
  3936. abbr = abbrs[abbrind:abbrs.find(0, abbrind)].decode()
  3937. ttis[i] = (timedelta(0, gmtoff), isdst, abbr)
  3938. ti = [None] * len(ut)
  3939. for i, idx in enumerate(type_indices):
  3940. ti[i] = ttis[idx]
  3941. self = cls(ut, ti)
  3942. return self
  3943. @classmethod
  3944. def fromname(cls, name):
  3945. path = os.path.join(cls.zoneroot, name)
  3946. with open(path, 'rb') as f:
  3947. return cls.fromfile(f)
  3948. EPOCHORDINAL = date(1970, 1, 1).toordinal()
  3949. def fromutc(self, dt):
  3950. """datetime in UTC -> datetime in local time."""
  3951. if not isinstance(dt, datetime):
  3952. raise TypeError("fromutc() requires a datetime argument")
  3953. if dt.tzinfo is not self:
  3954. raise ValueError("dt.tzinfo is not self")
  3955. timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
  3956. + dt.hour * 3600
  3957. + dt.minute * 60
  3958. + dt.second)
  3959. if timestamp < self.ut[1]:
  3960. tti = self.ti[0]
  3961. fold = 0
  3962. else:
  3963. idx = bisect.bisect_right(self.ut, timestamp)
  3964. assert self.ut[idx-1] <= timestamp
  3965. assert idx == len(self.ut) or timestamp < self.ut[idx]
  3966. tti_prev, tti = self.ti[idx-2:idx]
  3967. # Detect fold
  3968. shift = tti_prev[0] - tti[0]
  3969. fold = (shift > timedelta(0, timestamp - self.ut[idx-1]))
  3970. dt += tti[0]
  3971. if fold:
  3972. return dt.replace(fold=1)
  3973. else:
  3974. return dt
  3975. def _find_ti(self, dt, i):
  3976. timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
  3977. + dt.hour * 3600
  3978. + dt.minute * 60
  3979. + dt.second)
  3980. lt = self.lt[dt.fold]
  3981. idx = bisect.bisect_right(lt, timestamp)
  3982. return self.ti[max(0, idx - 1)][i]
  3983. def utcoffset(self, dt):
  3984. return self._find_ti(dt, 0)
  3985. def dst(self, dt):
  3986. isdst = self._find_ti(dt, 1)
  3987. # XXX: We cannot accurately determine the "save" value,
  3988. # so let's return 1h whenever DST is in effect. Since
  3989. # we don't use dst() in fromutc(), it is unlikely that
  3990. # it will be needed for anything more than bool(dst()).
  3991. return ZERO if isdst else HOUR
  3992. def tzname(self, dt):
  3993. return self._find_ti(dt, 2)
  3994. @classmethod
  3995. def zonenames(cls, zonedir=None):
  3996. if zonedir is None:
  3997. zonedir = cls.zoneroot
  3998. zone_tab = os.path.join(zonedir, 'zone.tab')
  3999. try:
  4000. f = open(zone_tab)
  4001. except OSError:
  4002. return
  4003. with f:
  4004. for line in f:
  4005. line = line.strip()
  4006. if line and not line.startswith('#'):
  4007. yield line.split()[2]
  4008. @classmethod
  4009. def stats(cls, start_year=1):
  4010. count = gap_count = fold_count = zeros_count = 0
  4011. min_gap = min_fold = timedelta.max
  4012. max_gap = max_fold = ZERO
  4013. min_gap_datetime = max_gap_datetime = datetime.min
  4014. min_gap_zone = max_gap_zone = None
  4015. min_fold_datetime = max_fold_datetime = datetime.min
  4016. min_fold_zone = max_fold_zone = None
  4017. stats_since = datetime(start_year, 1, 1) # Starting from 1970 eliminates a lot of noise
  4018. for zonename in cls.zonenames():
  4019. count += 1
  4020. tz = cls.fromname(zonename)
  4021. for dt, shift in tz.transitions():
  4022. if dt < stats_since:
  4023. continue
  4024. if shift > ZERO:
  4025. gap_count += 1
  4026. if (shift, dt) > (max_gap, max_gap_datetime):
  4027. max_gap = shift
  4028. max_gap_zone = zonename
  4029. max_gap_datetime = dt
  4030. if (shift, datetime.max - dt) < (min_gap, datetime.max - min_gap_datetime):
  4031. min_gap = shift
  4032. min_gap_zone = zonename
  4033. min_gap_datetime = dt
  4034. elif shift < ZERO:
  4035. fold_count += 1
  4036. shift = -shift
  4037. if (shift, dt) > (max_fold, max_fold_datetime):
  4038. max_fold = shift
  4039. max_fold_zone = zonename
  4040. max_fold_datetime = dt
  4041. if (shift, datetime.max - dt) < (min_fold, datetime.max - min_fold_datetime):
  4042. min_fold = shift
  4043. min_fold_zone = zonename
  4044. min_fold_datetime = dt
  4045. else:
  4046. zeros_count += 1
  4047. trans_counts = (gap_count, fold_count, zeros_count)
  4048. print("Number of zones: %5d" % count)
  4049. print("Number of transitions: %5d = %d (gaps) + %d (folds) + %d (zeros)" %
  4050. ((sum(trans_counts),) + trans_counts))
  4051. print("Min gap: %16s at %s in %s" % (min_gap, min_gap_datetime, min_gap_zone))
  4052. print("Max gap: %16s at %s in %s" % (max_gap, max_gap_datetime, max_gap_zone))
  4053. print("Min fold: %16s at %s in %s" % (min_fold, min_fold_datetime, min_fold_zone))
  4054. print("Max fold: %16s at %s in %s" % (max_fold, max_fold_datetime, max_fold_zone))
  4055. def transitions(self):
  4056. for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
  4057. shift = ti[0] - prev_ti[0]
  4058. yield datetime.utcfromtimestamp(t), shift
  4059. def nondst_folds(self):
  4060. """Find all folds with the same value of isdst on both sides of the transition."""
  4061. for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
  4062. shift = ti[0] - prev_ti[0]
  4063. if shift < ZERO and ti[1] == prev_ti[1]:
  4064. yield datetime.utcfromtimestamp(t), -shift, prev_ti[2], ti[2]
  4065. @classmethod
  4066. def print_all_nondst_folds(cls, same_abbr=False, start_year=1):
  4067. count = 0
  4068. for zonename in cls.zonenames():
  4069. tz = cls.fromname(zonename)
  4070. for dt, shift, prev_abbr, abbr in tz.nondst_folds():
  4071. if dt.year < start_year or same_abbr and prev_abbr != abbr:
  4072. continue
  4073. count += 1
  4074. print("%3d) %-30s %s %10s %5s -> %s" %
  4075. (count, zonename, dt, shift, prev_abbr, abbr))
  4076. def folds(self):
  4077. for t, shift in self.transitions():
  4078. if shift < ZERO:
  4079. yield t, -shift
  4080. def gaps(self):
  4081. for t, shift in self.transitions():
  4082. if shift > ZERO:
  4083. yield t, shift
  4084. def zeros(self):
  4085. for t, shift in self.transitions():
  4086. if not shift:
  4087. yield t
  4088. class ZoneInfoTest(unittest.TestCase):
  4089. zonename = 'America/New_York'
  4090. def setUp(self):
  4091. if sys.platform == "win32":
  4092. self.skipTest("Skipping zoneinfo tests on Windows")
  4093. try:
  4094. self.tz = ZoneInfo.fromname(self.zonename)
  4095. except FileNotFoundError as err:
  4096. self.skipTest("Skipping %s: %s" % (self.zonename, err))
  4097. def assertEquivDatetimes(self, a, b):
  4098. self.assertEqual((a.replace(tzinfo=None), a.fold, id(a.tzinfo)),
  4099. (b.replace(tzinfo=None), b.fold, id(b.tzinfo)))
  4100. def test_folds(self):
  4101. tz = self.tz
  4102. for dt, shift in tz.folds():
  4103. for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
  4104. udt = dt + x
  4105. ldt = tz.fromutc(udt.replace(tzinfo=tz))
  4106. self.assertEqual(ldt.fold, 1)
  4107. adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
  4108. self.assertEquivDatetimes(adt, ldt)
  4109. utcoffset = ldt.utcoffset()
  4110. self.assertEqual(ldt.replace(tzinfo=None), udt + utcoffset)
  4111. # Round trip
  4112. self.assertEquivDatetimes(ldt.astimezone(timezone.utc),
  4113. udt.replace(tzinfo=timezone.utc))
  4114. for x in [-timedelta.resolution, shift]:
  4115. udt = dt + x
  4116. udt = udt.replace(tzinfo=tz)
  4117. ldt = tz.fromutc(udt)
  4118. self.assertEqual(ldt.fold, 0)
  4119. def test_gaps(self):
  4120. tz = self.tz
  4121. for dt, shift in tz.gaps():
  4122. for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
  4123. udt = dt + x
  4124. udt = udt.replace(tzinfo=tz)
  4125. ldt = tz.fromutc(udt)
  4126. self.assertEqual(ldt.fold, 0)
  4127. adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
  4128. self.assertEquivDatetimes(adt, ldt)
  4129. utcoffset = ldt.utcoffset()
  4130. self.assertEqual(ldt.replace(tzinfo=None), udt.replace(tzinfo=None) + utcoffset)
  4131. # Create a local time inside the gap
  4132. ldt = tz.fromutc(dt.replace(tzinfo=tz)) - shift + x
  4133. self.assertLess(ldt.replace(fold=1).utcoffset(),
  4134. ldt.replace(fold=0).utcoffset(),
  4135. "At %s." % ldt)
  4136. for x in [-timedelta.resolution, shift]:
  4137. udt = dt + x
  4138. ldt = tz.fromutc(udt.replace(tzinfo=tz))
  4139. self.assertEqual(ldt.fold, 0)
  4140. def test_system_transitions(self):
  4141. if ('Riyadh8' in self.zonename or
  4142. # From tzdata NEWS file:
  4143. # The files solar87, solar88, and solar89 are no longer distributed.
  4144. # They were a negative experiment - that is, a demonstration that
  4145. # tz data can represent solar time only with some difficulty and error.
  4146. # Their presence in the distribution caused confusion, as Riyadh
  4147. # civil time was generally not solar time in those years.
  4148. self.zonename.startswith('right/')):
  4149. self.skipTest("Skipping %s" % self.zonename)
  4150. tz = self.tz
  4151. TZ = os.environ.get('TZ')
  4152. os.environ['TZ'] = self.zonename
  4153. try:
  4154. _time.tzset()
  4155. for udt, shift in tz.transitions():
  4156. if udt.year >= 2037:
  4157. # System support for times around the end of 32-bit time_t
  4158. # and later is flaky on many systems.
  4159. break
  4160. s0 = (udt - datetime(1970, 1, 1)) // SEC
  4161. ss = shift // SEC # shift seconds
  4162. for x in [-40 * 3600, -20*3600, -1, 0,
  4163. ss - 1, ss + 20 * 3600, ss + 40 * 3600]:
  4164. s = s0 + x
  4165. sdt = datetime.fromtimestamp(s)
  4166. tzdt = datetime.fromtimestamp(s, tz).replace(tzinfo=None)
  4167. self.assertEquivDatetimes(sdt, tzdt)
  4168. s1 = sdt.timestamp()
  4169. self.assertEqual(s, s1)
  4170. if ss > 0: # gap
  4171. # Create local time inside the gap
  4172. dt = datetime.fromtimestamp(s0) - shift / 2
  4173. ts0 = dt.timestamp()
  4174. ts1 = dt.replace(fold=1).timestamp()
  4175. self.assertEqual(ts0, s0 + ss / 2)
  4176. self.assertEqual(ts1, s0 - ss / 2)
  4177. finally:
  4178. if TZ is None:
  4179. del os.environ['TZ']
  4180. else:
  4181. os.environ['TZ'] = TZ
  4182. _time.tzset()
  4183. class ZoneInfoCompleteTest(unittest.TestSuite):
  4184. def __init__(self):
  4185. tests = []
  4186. if is_resource_enabled('tzdata'):
  4187. for name in ZoneInfo.zonenames():
  4188. Test = type('ZoneInfoTest[%s]' % name, (ZoneInfoTest,), {})
  4189. Test.zonename = name
  4190. for method in dir(Test):
  4191. if method.startswith('test_'):
  4192. tests.append(Test(method))
  4193. super().__init__(tests)
  4194. # Iran had a sub-minute UTC offset before 1946.
  4195. class IranTest(ZoneInfoTest):
  4196. zonename = 'Asia/Tehran'
  4197. def load_tests(loader, standard_tests, pattern):
  4198. standard_tests.addTest(ZoneInfoCompleteTest())
  4199. return standard_tests
  4200. if __name__ == "__main__":
  4201. unittest.main()