test_dates.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823
  1. import datetime
  2. try:
  3. from contextlib import nullcontext
  4. except ImportError:
  5. from contextlib import ExitStack as nullcontext # Py 3.6.
  6. import dateutil.tz
  7. import dateutil.rrule
  8. import numpy as np
  9. import pytest
  10. from matplotlib import rc_context
  11. from matplotlib.cbook import MatplotlibDeprecationWarning
  12. import matplotlib.dates as mdates
  13. import matplotlib.pyplot as plt
  14. from matplotlib.testing.decorators import image_comparison
  15. import matplotlib.ticker as mticker
  16. def test_date_numpyx():
  17. # test that numpy dates work properly...
  18. base = datetime.datetime(2017, 1, 1)
  19. time = [base + datetime.timedelta(days=x) for x in range(0, 3)]
  20. timenp = np.array(time, dtype='datetime64[ns]')
  21. data = np.array([0., 2., 1.])
  22. fig = plt.figure(figsize=(10, 2))
  23. ax = fig.add_subplot(1, 1, 1)
  24. h, = ax.plot(time, data)
  25. hnp, = ax.plot(timenp, data)
  26. np.testing.assert_equal(h.get_xdata(orig=False), hnp.get_xdata(orig=False))
  27. fig = plt.figure(figsize=(10, 2))
  28. ax = fig.add_subplot(1, 1, 1)
  29. h, = ax.plot(data, time)
  30. hnp, = ax.plot(data, timenp)
  31. np.testing.assert_equal(h.get_ydata(orig=False), hnp.get_ydata(orig=False))
  32. @pytest.mark.parametrize('t0', [datetime.datetime(2017, 1, 1, 0, 1, 1),
  33. [datetime.datetime(2017, 1, 1, 0, 1, 1),
  34. datetime.datetime(2017, 1, 1, 1, 1, 1)],
  35. [[datetime.datetime(2017, 1, 1, 0, 1, 1),
  36. datetime.datetime(2017, 1, 1, 1, 1, 1)],
  37. [datetime.datetime(2017, 1, 1, 2, 1, 1),
  38. datetime.datetime(2017, 1, 1, 3, 1, 1)]]])
  39. @pytest.mark.parametrize('dtype', ['datetime64[s]',
  40. 'datetime64[us]',
  41. 'datetime64[ms]',
  42. 'datetime64[ns]'])
  43. def test_date_date2num_numpy(t0, dtype):
  44. time = mdates.date2num(t0)
  45. tnp = np.array(t0, dtype=dtype)
  46. nptime = mdates.date2num(tnp)
  47. np.testing.assert_equal(time, nptime)
  48. @pytest.mark.parametrize('dtype', ['datetime64[s]',
  49. 'datetime64[us]',
  50. 'datetime64[ms]',
  51. 'datetime64[ns]'])
  52. def test_date2num_NaT(dtype):
  53. t0 = datetime.datetime(2017, 1, 1, 0, 1, 1)
  54. tmpl = [mdates.date2num(t0), np.nan]
  55. tnp = np.array([t0, 'NaT'], dtype=dtype)
  56. nptime = mdates.date2num(tnp)
  57. np.testing.assert_array_equal(tmpl, nptime)
  58. @pytest.mark.parametrize('units', ['s', 'ms', 'us', 'ns'])
  59. def test_date2num_NaT_scalar(units):
  60. tmpl = mdates.date2num(np.datetime64('NaT', units))
  61. assert np.isnan(tmpl)
  62. @image_comparison(['date_empty.png'])
  63. def test_date_empty():
  64. # make sure we do the right thing when told to plot dates even
  65. # if no date data has been presented, cf
  66. # http://sourceforge.net/tracker/?func=detail&aid=2850075&group_id=80706&atid=560720
  67. fig = plt.figure()
  68. ax = fig.add_subplot(1, 1, 1)
  69. ax.xaxis_date()
  70. @image_comparison(['date_axhspan.png'])
  71. def test_date_axhspan():
  72. # test ax hspan with date inputs
  73. t0 = datetime.datetime(2009, 1, 20)
  74. tf = datetime.datetime(2009, 1, 21)
  75. fig = plt.figure()
  76. ax = fig.add_subplot(1, 1, 1)
  77. ax.axhspan(t0, tf, facecolor="blue", alpha=0.25)
  78. ax.set_ylim(t0 - datetime.timedelta(days=5),
  79. tf + datetime.timedelta(days=5))
  80. fig.subplots_adjust(left=0.25)
  81. @image_comparison(['date_axvspan.png'])
  82. def test_date_axvspan():
  83. # test ax hspan with date inputs
  84. t0 = datetime.datetime(2000, 1, 20)
  85. tf = datetime.datetime(2010, 1, 21)
  86. fig = plt.figure()
  87. ax = fig.add_subplot(1, 1, 1)
  88. ax.axvspan(t0, tf, facecolor="blue", alpha=0.25)
  89. ax.set_xlim(t0 - datetime.timedelta(days=720),
  90. tf + datetime.timedelta(days=720))
  91. fig.autofmt_xdate()
  92. @image_comparison(['date_axhline.png'])
  93. def test_date_axhline():
  94. # test ax hline with date inputs
  95. t0 = datetime.datetime(2009, 1, 20)
  96. tf = datetime.datetime(2009, 1, 31)
  97. fig = plt.figure()
  98. ax = fig.add_subplot(1, 1, 1)
  99. ax.axhline(t0, color="blue", lw=3)
  100. ax.set_ylim(t0 - datetime.timedelta(days=5),
  101. tf + datetime.timedelta(days=5))
  102. fig.subplots_adjust(left=0.25)
  103. @image_comparison(['date_axvline.png'])
  104. def test_date_axvline():
  105. # test ax hline with date inputs
  106. t0 = datetime.datetime(2000, 1, 20)
  107. tf = datetime.datetime(2000, 1, 21)
  108. fig = plt.figure()
  109. ax = fig.add_subplot(1, 1, 1)
  110. ax.axvline(t0, color="red", lw=3)
  111. ax.set_xlim(t0 - datetime.timedelta(days=5),
  112. tf + datetime.timedelta(days=5))
  113. fig.autofmt_xdate()
  114. def test_too_many_date_ticks(caplog):
  115. # Attempt to test SF 2715172, see
  116. # https://sourceforge.net/tracker/?func=detail&aid=2715172&group_id=80706&atid=560720
  117. # setting equal datetimes triggers and expander call in
  118. # transforms.nonsingular which results in too many ticks in the
  119. # DayLocator. This should emit a log at WARNING level.
  120. caplog.set_level("WARNING")
  121. t0 = datetime.datetime(2000, 1, 20)
  122. tf = datetime.datetime(2000, 1, 20)
  123. fig = plt.figure()
  124. ax = fig.add_subplot(1, 1, 1)
  125. with pytest.warns(UserWarning) as rec:
  126. ax.set_xlim((t0, tf), auto=True)
  127. assert len(rec) == 1
  128. assert \
  129. 'Attempting to set identical left == right' in str(rec[0].message)
  130. ax.plot([], [])
  131. ax.xaxis.set_major_locator(mdates.DayLocator())
  132. fig.canvas.draw()
  133. # The warning is emitted multiple times because the major locator is also
  134. # called both when placing the minor ticks (for overstriking detection) and
  135. # during tick label positioning.
  136. assert caplog.records and all(
  137. record.name == "matplotlib.ticker" and record.levelname == "WARNING"
  138. for record in caplog.records)
  139. @image_comparison(['RRuleLocator_bounds.png'])
  140. def test_RRuleLocator():
  141. import matplotlib.testing.jpl_units as units
  142. units.register()
  143. # This will cause the RRuleLocator to go out of bounds when it tries
  144. # to add padding to the limits, so we make sure it caps at the correct
  145. # boundary values.
  146. t0 = datetime.datetime(1000, 1, 1)
  147. tf = datetime.datetime(6000, 1, 1)
  148. fig = plt.figure()
  149. ax = plt.subplot(111)
  150. ax.set_autoscale_on(True)
  151. ax.plot([t0, tf], [0.0, 1.0], marker='o')
  152. rrule = mdates.rrulewrapper(dateutil.rrule.YEARLY, interval=500)
  153. locator = mdates.RRuleLocator(rrule)
  154. ax.xaxis.set_major_locator(locator)
  155. ax.xaxis.set_major_formatter(mdates.AutoDateFormatter(locator))
  156. ax.autoscale_view()
  157. fig.autofmt_xdate()
  158. def test_RRuleLocator_dayrange():
  159. loc = mdates.DayLocator()
  160. x1 = datetime.datetime(year=1, month=1, day=1, tzinfo=mdates.UTC)
  161. y1 = datetime.datetime(year=1, month=1, day=16, tzinfo=mdates.UTC)
  162. loc.tick_values(x1, y1)
  163. # On success, no overflow error shall be thrown
  164. @image_comparison(['DateFormatter_fractionalSeconds.png'])
  165. def test_DateFormatter():
  166. import matplotlib.testing.jpl_units as units
  167. units.register()
  168. # Lets make sure that DateFormatter will allow us to have tick marks
  169. # at intervals of fractional seconds.
  170. t0 = datetime.datetime(2001, 1, 1, 0, 0, 0)
  171. tf = datetime.datetime(2001, 1, 1, 0, 0, 1)
  172. fig = plt.figure()
  173. ax = plt.subplot(111)
  174. ax.set_autoscale_on(True)
  175. ax.plot([t0, tf], [0.0, 1.0], marker='o')
  176. # rrule = mpldates.rrulewrapper( dateutil.rrule.YEARLY, interval=500 )
  177. # locator = mpldates.RRuleLocator( rrule )
  178. # ax.xaxis.set_major_locator( locator )
  179. # ax.xaxis.set_major_formatter( mpldates.AutoDateFormatter(locator) )
  180. ax.autoscale_view()
  181. fig.autofmt_xdate()
  182. def test_locator_set_formatter():
  183. """
  184. Test if setting the locator only will update the AutoDateFormatter to use
  185. the new locator.
  186. """
  187. plt.rcParams["date.autoformatter.minute"] = "%d %H:%M"
  188. t = [datetime.datetime(2018, 9, 30, 8, 0),
  189. datetime.datetime(2018, 9, 30, 8, 59),
  190. datetime.datetime(2018, 9, 30, 10, 30)]
  191. x = [2, 3, 1]
  192. fig, ax = plt.subplots()
  193. ax.plot(t, x)
  194. ax.xaxis.set_major_locator(mdates.MinuteLocator((0, 30)))
  195. fig.canvas.draw()
  196. ticklabels = [tl.get_text() for tl in ax.get_xticklabels()]
  197. expected = ['30 08:00', '30 08:30', '30 09:00',
  198. '30 09:30', '30 10:00', '30 10:30']
  199. assert ticklabels == expected
  200. ax.xaxis.set_major_locator(mticker.NullLocator())
  201. ax.xaxis.set_minor_locator(mdates.MinuteLocator((5, 55)))
  202. decoy_loc = mdates.MinuteLocator((12, 27))
  203. ax.xaxis.set_minor_formatter(mdates.AutoDateFormatter(decoy_loc))
  204. ax.xaxis.set_minor_locator(mdates.MinuteLocator((15, 45)))
  205. fig.canvas.draw()
  206. ticklabels = [tl.get_text() for tl in ax.get_xticklabels(which="minor")]
  207. expected = ['30 08:15', '30 08:45', '30 09:15', '30 09:45', '30 10:15']
  208. assert ticklabels == expected
  209. def test_date_formatter_callable():
  210. class _Locator:
  211. def _get_unit(self): return -11
  212. def callable_formatting_function(dates, _):
  213. return [dt.strftime('%d-%m//%Y') for dt in dates]
  214. formatter = mdates.AutoDateFormatter(_Locator())
  215. formatter.scaled[-10] = callable_formatting_function
  216. assert formatter([datetime.datetime(2014, 12, 25)]) == ['25-12//2014']
  217. def test_drange():
  218. """
  219. This test should check if drange works as expected, and if all the
  220. rounding errors are fixed
  221. """
  222. start = datetime.datetime(2011, 1, 1, tzinfo=mdates.UTC)
  223. end = datetime.datetime(2011, 1, 2, tzinfo=mdates.UTC)
  224. delta = datetime.timedelta(hours=1)
  225. # We expect 24 values in drange(start, end, delta), because drange returns
  226. # dates from an half open interval [start, end)
  227. assert len(mdates.drange(start, end, delta)) == 24
  228. # if end is a little bit later, we expect the range to contain one element
  229. # more
  230. end = end + datetime.timedelta(microseconds=1)
  231. assert len(mdates.drange(start, end, delta)) == 25
  232. # reset end
  233. end = datetime.datetime(2011, 1, 2, tzinfo=mdates.UTC)
  234. # and tst drange with "complicated" floats:
  235. # 4 hours = 1/6 day, this is an "dangerous" float
  236. delta = datetime.timedelta(hours=4)
  237. daterange = mdates.drange(start, end, delta)
  238. assert len(daterange) == 6
  239. assert mdates.num2date(daterange[-1]) == (end - delta)
  240. def test_empty_date_with_year_formatter():
  241. # exposes sf bug 2861426:
  242. # https://sourceforge.net/tracker/?func=detail&aid=2861426&group_id=80706&atid=560720
  243. # update: I am no longer believe this is a bug, as I commented on
  244. # the tracker. The question is now: what to do with this test
  245. import matplotlib.dates as dates
  246. fig = plt.figure()
  247. ax = fig.add_subplot(111)
  248. yearFmt = dates.DateFormatter('%Y')
  249. ax.xaxis.set_major_formatter(yearFmt)
  250. with pytest.raises(ValueError):
  251. fig.canvas.draw()
  252. def test_auto_date_locator():
  253. def _create_auto_date_locator(date1, date2):
  254. locator = mdates.AutoDateLocator(interval_multiples=False)
  255. locator.create_dummy_axis()
  256. locator.set_view_interval(mdates.date2num(date1),
  257. mdates.date2num(date2))
  258. return locator
  259. d1 = datetime.datetime(1990, 1, 1)
  260. results = ([datetime.timedelta(weeks=52 * 200),
  261. ['1990-01-01 00:00:00+00:00', '2010-01-01 00:00:00+00:00',
  262. '2030-01-01 00:00:00+00:00', '2050-01-01 00:00:00+00:00',
  263. '2070-01-01 00:00:00+00:00', '2090-01-01 00:00:00+00:00',
  264. '2110-01-01 00:00:00+00:00', '2130-01-01 00:00:00+00:00',
  265. '2150-01-01 00:00:00+00:00', '2170-01-01 00:00:00+00:00']
  266. ],
  267. [datetime.timedelta(weeks=52),
  268. ['1990-01-01 00:00:00+00:00', '1990-02-01 00:00:00+00:00',
  269. '1990-03-01 00:00:00+00:00', '1990-04-01 00:00:00+00:00',
  270. '1990-05-01 00:00:00+00:00', '1990-06-01 00:00:00+00:00',
  271. '1990-07-01 00:00:00+00:00', '1990-08-01 00:00:00+00:00',
  272. '1990-09-01 00:00:00+00:00', '1990-10-01 00:00:00+00:00',
  273. '1990-11-01 00:00:00+00:00', '1990-12-01 00:00:00+00:00']
  274. ],
  275. [datetime.timedelta(days=141),
  276. ['1990-01-05 00:00:00+00:00', '1990-01-26 00:00:00+00:00',
  277. '1990-02-16 00:00:00+00:00', '1990-03-09 00:00:00+00:00',
  278. '1990-03-30 00:00:00+00:00', '1990-04-20 00:00:00+00:00',
  279. '1990-05-11 00:00:00+00:00']
  280. ],
  281. [datetime.timedelta(days=40),
  282. ['1990-01-03 00:00:00+00:00', '1990-01-10 00:00:00+00:00',
  283. '1990-01-17 00:00:00+00:00', '1990-01-24 00:00:00+00:00',
  284. '1990-01-31 00:00:00+00:00', '1990-02-07 00:00:00+00:00']
  285. ],
  286. [datetime.timedelta(hours=40),
  287. ['1990-01-01 00:00:00+00:00', '1990-01-01 04:00:00+00:00',
  288. '1990-01-01 08:00:00+00:00', '1990-01-01 12:00:00+00:00',
  289. '1990-01-01 16:00:00+00:00', '1990-01-01 20:00:00+00:00',
  290. '1990-01-02 00:00:00+00:00', '1990-01-02 04:00:00+00:00',
  291. '1990-01-02 08:00:00+00:00', '1990-01-02 12:00:00+00:00',
  292. '1990-01-02 16:00:00+00:00']
  293. ],
  294. [datetime.timedelta(minutes=20),
  295. ['1990-01-01 00:00:00+00:00', '1990-01-01 00:05:00+00:00',
  296. '1990-01-01 00:10:00+00:00', '1990-01-01 00:15:00+00:00',
  297. '1990-01-01 00:20:00+00:00']
  298. ],
  299. [datetime.timedelta(seconds=40),
  300. ['1990-01-01 00:00:00+00:00', '1990-01-01 00:00:05+00:00',
  301. '1990-01-01 00:00:10+00:00', '1990-01-01 00:00:15+00:00',
  302. '1990-01-01 00:00:20+00:00', '1990-01-01 00:00:25+00:00',
  303. '1990-01-01 00:00:30+00:00', '1990-01-01 00:00:35+00:00',
  304. '1990-01-01 00:00:40+00:00']
  305. ],
  306. [datetime.timedelta(microseconds=1500),
  307. ['1989-12-31 23:59:59.999500+00:00',
  308. '1990-01-01 00:00:00+00:00',
  309. '1990-01-01 00:00:00.000500+00:00',
  310. '1990-01-01 00:00:00.001000+00:00',
  311. '1990-01-01 00:00:00.001500+00:00']
  312. ],
  313. )
  314. for t_delta, expected in results:
  315. d2 = d1 + t_delta
  316. locator = _create_auto_date_locator(d1, d2)
  317. with (pytest.warns(UserWarning) if t_delta.microseconds
  318. else nullcontext()):
  319. assert list(map(str, mdates.num2date(locator()))) == expected
  320. def test_auto_date_locator_intmult():
  321. def _create_auto_date_locator(date1, date2):
  322. locator = mdates.AutoDateLocator(interval_multiples=True)
  323. locator.create_dummy_axis()
  324. locator.set_view_interval(mdates.date2num(date1),
  325. mdates.date2num(date2))
  326. return locator
  327. results = ([datetime.timedelta(weeks=52 * 200),
  328. ['1980-01-01 00:00:00+00:00', '2000-01-01 00:00:00+00:00',
  329. '2020-01-01 00:00:00+00:00', '2040-01-01 00:00:00+00:00',
  330. '2060-01-01 00:00:00+00:00', '2080-01-01 00:00:00+00:00',
  331. '2100-01-01 00:00:00+00:00', '2120-01-01 00:00:00+00:00',
  332. '2140-01-01 00:00:00+00:00', '2160-01-01 00:00:00+00:00',
  333. '2180-01-01 00:00:00+00:00', '2200-01-01 00:00:00+00:00']
  334. ],
  335. [datetime.timedelta(weeks=52),
  336. ['1997-01-01 00:00:00+00:00', '1997-02-01 00:00:00+00:00',
  337. '1997-03-01 00:00:00+00:00', '1997-04-01 00:00:00+00:00',
  338. '1997-05-01 00:00:00+00:00', '1997-06-01 00:00:00+00:00',
  339. '1997-07-01 00:00:00+00:00', '1997-08-01 00:00:00+00:00',
  340. '1997-09-01 00:00:00+00:00', '1997-10-01 00:00:00+00:00',
  341. '1997-11-01 00:00:00+00:00', '1997-12-01 00:00:00+00:00']
  342. ],
  343. [datetime.timedelta(days=141),
  344. ['1997-01-01 00:00:00+00:00', '1997-01-22 00:00:00+00:00',
  345. '1997-02-01 00:00:00+00:00', '1997-02-22 00:00:00+00:00',
  346. '1997-03-01 00:00:00+00:00', '1997-03-22 00:00:00+00:00',
  347. '1997-04-01 00:00:00+00:00', '1997-04-22 00:00:00+00:00',
  348. '1997-05-01 00:00:00+00:00', '1997-05-22 00:00:00+00:00']
  349. ],
  350. [datetime.timedelta(days=40),
  351. ['1997-01-01 00:00:00+00:00', '1997-01-05 00:00:00+00:00',
  352. '1997-01-09 00:00:00+00:00', '1997-01-13 00:00:00+00:00',
  353. '1997-01-17 00:00:00+00:00', '1997-01-21 00:00:00+00:00',
  354. '1997-01-25 00:00:00+00:00', '1997-01-29 00:00:00+00:00',
  355. '1997-02-01 00:00:00+00:00', '1997-02-05 00:00:00+00:00',
  356. '1997-02-09 00:00:00+00:00']
  357. ],
  358. [datetime.timedelta(hours=40),
  359. ['1997-01-01 00:00:00+00:00', '1997-01-01 04:00:00+00:00',
  360. '1997-01-01 08:00:00+00:00', '1997-01-01 12:00:00+00:00',
  361. '1997-01-01 16:00:00+00:00', '1997-01-01 20:00:00+00:00',
  362. '1997-01-02 00:00:00+00:00', '1997-01-02 04:00:00+00:00',
  363. '1997-01-02 08:00:00+00:00', '1997-01-02 12:00:00+00:00',
  364. '1997-01-02 16:00:00+00:00']
  365. ],
  366. [datetime.timedelta(minutes=20),
  367. ['1997-01-01 00:00:00+00:00', '1997-01-01 00:05:00+00:00',
  368. '1997-01-01 00:10:00+00:00', '1997-01-01 00:15:00+00:00',
  369. '1997-01-01 00:20:00+00:00']
  370. ],
  371. [datetime.timedelta(seconds=40),
  372. ['1997-01-01 00:00:00+00:00', '1997-01-01 00:00:05+00:00',
  373. '1997-01-01 00:00:10+00:00', '1997-01-01 00:00:15+00:00',
  374. '1997-01-01 00:00:20+00:00', '1997-01-01 00:00:25+00:00',
  375. '1997-01-01 00:00:30+00:00', '1997-01-01 00:00:35+00:00',
  376. '1997-01-01 00:00:40+00:00']
  377. ],
  378. [datetime.timedelta(microseconds=1500),
  379. ['1996-12-31 23:59:59.999500+00:00',
  380. '1997-01-01 00:00:00+00:00',
  381. '1997-01-01 00:00:00.000500+00:00',
  382. '1997-01-01 00:00:00.001000+00:00',
  383. '1997-01-01 00:00:00.001500+00:00']
  384. ],
  385. )
  386. d1 = datetime.datetime(1997, 1, 1)
  387. for t_delta, expected in results:
  388. d2 = d1 + t_delta
  389. locator = _create_auto_date_locator(d1, d2)
  390. with (pytest.warns(UserWarning) if t_delta.microseconds
  391. else nullcontext()):
  392. assert list(map(str, mdates.num2date(locator()))) == expected
  393. def test_concise_formatter():
  394. def _create_auto_date_locator(date1, date2):
  395. fig, ax = plt.subplots()
  396. locator = mdates.AutoDateLocator(interval_multiples=True)
  397. formatter = mdates.ConciseDateFormatter(locator)
  398. ax.yaxis.set_major_locator(locator)
  399. ax.yaxis.set_major_formatter(formatter)
  400. ax.set_ylim(date1, date2)
  401. fig.canvas.draw()
  402. sts = []
  403. for st in ax.get_yticklabels():
  404. sts += [st.get_text()]
  405. return sts
  406. d1 = datetime.datetime(1997, 1, 1)
  407. results = ([datetime.timedelta(weeks=52 * 200),
  408. [str(t) for t in range(1980, 2201, 20)]
  409. ],
  410. [datetime.timedelta(weeks=52),
  411. ['1997', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug',
  412. 'Sep', 'Oct', 'Nov', 'Dec']
  413. ],
  414. [datetime.timedelta(days=141),
  415. ['Jan', '22', 'Feb', '22', 'Mar', '22', 'Apr', '22',
  416. 'May', '22']
  417. ],
  418. [datetime.timedelta(days=40),
  419. ['Jan', '05', '09', '13', '17', '21', '25', '29', 'Feb',
  420. '05', '09']
  421. ],
  422. [datetime.timedelta(hours=40),
  423. ['Jan-01', '04:00', '08:00', '12:00', '16:00', '20:00',
  424. 'Jan-02', '04:00', '08:00', '12:00', '16:00']
  425. ],
  426. [datetime.timedelta(minutes=20),
  427. ['00:00', '00:05', '00:10', '00:15', '00:20']
  428. ],
  429. [datetime.timedelta(seconds=40),
  430. ['00:00', '05', '10', '15', '20', '25', '30', '35', '40']
  431. ],
  432. [datetime.timedelta(seconds=2),
  433. ['59.5', '00:00', '00.5', '01.0', '01.5', '02.0', '02.5']
  434. ],
  435. )
  436. for t_delta, expected in results:
  437. d2 = d1 + t_delta
  438. strings = _create_auto_date_locator(d1, d2)
  439. assert strings == expected
  440. def test_concise_formatter_tz():
  441. def _create_auto_date_locator(date1, date2, tz):
  442. fig, ax = plt.subplots()
  443. locator = mdates.AutoDateLocator(interval_multiples=True)
  444. formatter = mdates.ConciseDateFormatter(locator, tz=tz)
  445. ax.yaxis.set_major_locator(locator)
  446. ax.yaxis.set_major_formatter(formatter)
  447. ax.set_ylim(date1, date2)
  448. fig.canvas.draw()
  449. sts = []
  450. for st in ax.get_yticklabels():
  451. sts += [st.get_text()]
  452. return sts, ax.yaxis.get_offset_text().get_text()
  453. d1 = datetime.datetime(1997, 1, 1).replace(tzinfo=datetime.timezone.utc)
  454. results = ([datetime.timedelta(hours=40),
  455. ['03:00', '07:00', '11:00', '15:00', '19:00', '23:00',
  456. '03:00', '07:00', '11:00', '15:00', '19:00'],
  457. "1997-Jan-02"
  458. ],
  459. [datetime.timedelta(minutes=20),
  460. ['03:00', '03:05', '03:10', '03:15', '03:20'],
  461. "1997-Jan-01"
  462. ],
  463. [datetime.timedelta(seconds=40),
  464. ['03:00', '05', '10', '15', '20', '25', '30', '35', '40'],
  465. "1997-Jan-01 03:00"
  466. ],
  467. [datetime.timedelta(seconds=2),
  468. ['59.5', '03:00', '00.5', '01.0', '01.5', '02.0', '02.5'],
  469. "1997-Jan-01 03:00"
  470. ],
  471. )
  472. new_tz = datetime.timezone(datetime.timedelta(hours=3))
  473. for t_delta, expected_strings, expected_offset in results:
  474. d2 = d1 + t_delta
  475. strings, offset = _create_auto_date_locator(d1, d2, new_tz)
  476. assert strings == expected_strings
  477. assert offset == expected_offset
  478. def test_auto_date_locator_intmult_tz():
  479. def _create_auto_date_locator(date1, date2, tz):
  480. locator = mdates.AutoDateLocator(interval_multiples=True, tz=tz)
  481. locator.create_dummy_axis()
  482. locator.set_view_interval(mdates.date2num(date1),
  483. mdates.date2num(date2))
  484. return locator
  485. results = ([datetime.timedelta(weeks=52*200),
  486. ['1980-01-01 00:00:00-08:00', '2000-01-01 00:00:00-08:00',
  487. '2020-01-01 00:00:00-08:00', '2040-01-01 00:00:00-08:00',
  488. '2060-01-01 00:00:00-08:00', '2080-01-01 00:00:00-08:00',
  489. '2100-01-01 00:00:00-08:00', '2120-01-01 00:00:00-08:00',
  490. '2140-01-01 00:00:00-08:00', '2160-01-01 00:00:00-08:00',
  491. '2180-01-01 00:00:00-08:00', '2200-01-01 00:00:00-08:00']
  492. ],
  493. [datetime.timedelta(weeks=52),
  494. ['1997-01-01 00:00:00-08:00', '1997-02-01 00:00:00-08:00',
  495. '1997-03-01 00:00:00-08:00', '1997-04-01 00:00:00-08:00',
  496. '1997-05-01 00:00:00-07:00', '1997-06-01 00:00:00-07:00',
  497. '1997-07-01 00:00:00-07:00', '1997-08-01 00:00:00-07:00',
  498. '1997-09-01 00:00:00-07:00', '1997-10-01 00:00:00-07:00',
  499. '1997-11-01 00:00:00-08:00', '1997-12-01 00:00:00-08:00']
  500. ],
  501. [datetime.timedelta(days=141),
  502. ['1997-01-01 00:00:00-08:00', '1997-01-22 00:00:00-08:00',
  503. '1997-02-01 00:00:00-08:00', '1997-02-22 00:00:00-08:00',
  504. '1997-03-01 00:00:00-08:00', '1997-03-22 00:00:00-08:00',
  505. '1997-04-01 00:00:00-08:00', '1997-04-22 00:00:00-07:00',
  506. '1997-05-01 00:00:00-07:00', '1997-05-22 00:00:00-07:00']
  507. ],
  508. [datetime.timedelta(days=40),
  509. ['1997-01-01 00:00:00-08:00', '1997-01-05 00:00:00-08:00',
  510. '1997-01-09 00:00:00-08:00', '1997-01-13 00:00:00-08:00',
  511. '1997-01-17 00:00:00-08:00', '1997-01-21 00:00:00-08:00',
  512. '1997-01-25 00:00:00-08:00', '1997-01-29 00:00:00-08:00',
  513. '1997-02-01 00:00:00-08:00', '1997-02-05 00:00:00-08:00',
  514. '1997-02-09 00:00:00-08:00']
  515. ],
  516. [datetime.timedelta(hours=40),
  517. ['1997-01-01 00:00:00-08:00', '1997-01-01 04:00:00-08:00',
  518. '1997-01-01 08:00:00-08:00', '1997-01-01 12:00:00-08:00',
  519. '1997-01-01 16:00:00-08:00', '1997-01-01 20:00:00-08:00',
  520. '1997-01-02 00:00:00-08:00', '1997-01-02 04:00:00-08:00',
  521. '1997-01-02 08:00:00-08:00', '1997-01-02 12:00:00-08:00',
  522. '1997-01-02 16:00:00-08:00']
  523. ],
  524. [datetime.timedelta(minutes=20),
  525. ['1997-01-01 00:00:00-08:00', '1997-01-01 00:05:00-08:00',
  526. '1997-01-01 00:10:00-08:00', '1997-01-01 00:15:00-08:00',
  527. '1997-01-01 00:20:00-08:00']
  528. ],
  529. [datetime.timedelta(seconds=40),
  530. ['1997-01-01 00:00:00-08:00', '1997-01-01 00:00:05-08:00',
  531. '1997-01-01 00:00:10-08:00', '1997-01-01 00:00:15-08:00',
  532. '1997-01-01 00:00:20-08:00', '1997-01-01 00:00:25-08:00',
  533. '1997-01-01 00:00:30-08:00', '1997-01-01 00:00:35-08:00',
  534. '1997-01-01 00:00:40-08:00']
  535. ]
  536. )
  537. tz = dateutil.tz.gettz('Canada/Pacific')
  538. d1 = datetime.datetime(1997, 1, 1, tzinfo=tz)
  539. for t_delta, expected in results:
  540. with rc_context({'_internal.classic_mode': False}):
  541. d2 = d1 + t_delta
  542. locator = _create_auto_date_locator(d1, d2, tz)
  543. st = list(map(str, mdates.num2date(locator(), tz=tz)))
  544. assert st == expected
  545. @image_comparison(['date_inverted_limit.png'])
  546. def test_date_inverted_limit():
  547. # test ax hline with date inputs
  548. t0 = datetime.datetime(2009, 1, 20)
  549. tf = datetime.datetime(2009, 1, 31)
  550. fig = plt.figure()
  551. ax = fig.add_subplot(1, 1, 1)
  552. ax.axhline(t0, color="blue", lw=3)
  553. ax.set_ylim(t0 - datetime.timedelta(days=5),
  554. tf + datetime.timedelta(days=5))
  555. ax.invert_yaxis()
  556. fig.subplots_adjust(left=0.25)
  557. def _test_date2num_dst(date_range, tz_convert):
  558. # Timezones
  559. BRUSSELS = dateutil.tz.gettz('Europe/Brussels')
  560. UTC = mdates.UTC
  561. # Create a list of timezone-aware datetime objects in UTC
  562. # Interval is 0b0.0000011 days, to prevent float rounding issues
  563. dtstart = datetime.datetime(2014, 3, 30, 0, 0, tzinfo=UTC)
  564. interval = datetime.timedelta(minutes=33, seconds=45)
  565. interval_days = 0.0234375 # 2025 / 86400 seconds
  566. N = 8
  567. dt_utc = date_range(start=dtstart, freq=interval, periods=N)
  568. dt_bxl = tz_convert(dt_utc, BRUSSELS)
  569. expected_ordinalf = [735322.0 + (i * interval_days) for i in range(N)]
  570. actual_ordinalf = list(mdates.date2num(dt_bxl))
  571. assert actual_ordinalf == expected_ordinalf
  572. def test_date2num_dst():
  573. # Test for github issue #3896, but in date2num around DST transitions
  574. # with a timezone-aware pandas date_range object.
  575. class dt_tzaware(datetime.datetime):
  576. """
  577. This bug specifically occurs because of the normalization behavior of
  578. pandas Timestamp objects, so in order to replicate it, we need a
  579. datetime-like object that applies timezone normalization after
  580. subtraction.
  581. """
  582. def __sub__(self, other):
  583. r = super().__sub__(other)
  584. tzinfo = getattr(r, 'tzinfo', None)
  585. if tzinfo is not None:
  586. localizer = getattr(tzinfo, 'normalize', None)
  587. if localizer is not None:
  588. r = tzinfo.normalize(r)
  589. if isinstance(r, datetime.datetime):
  590. r = self.mk_tzaware(r)
  591. return r
  592. def __add__(self, other):
  593. return self.mk_tzaware(super().__add__(other))
  594. def astimezone(self, tzinfo):
  595. dt = super().astimezone(tzinfo)
  596. return self.mk_tzaware(dt)
  597. @classmethod
  598. def mk_tzaware(cls, datetime_obj):
  599. kwargs = {}
  600. attrs = ('year',
  601. 'month',
  602. 'day',
  603. 'hour',
  604. 'minute',
  605. 'second',
  606. 'microsecond',
  607. 'tzinfo')
  608. for attr in attrs:
  609. val = getattr(datetime_obj, attr, None)
  610. if val is not None:
  611. kwargs[attr] = val
  612. return cls(**kwargs)
  613. # Define a date_range function similar to pandas.date_range
  614. def date_range(start, freq, periods):
  615. dtstart = dt_tzaware.mk_tzaware(start)
  616. return [dtstart + (i * freq) for i in range(periods)]
  617. # Define a tz_convert function that converts a list to a new time zone.
  618. def tz_convert(dt_list, tzinfo):
  619. return [d.astimezone(tzinfo) for d in dt_list]
  620. _test_date2num_dst(date_range, tz_convert)
  621. def test_date2num_dst_pandas(pd):
  622. # Test for github issue #3896, but in date2num around DST transitions
  623. # with a timezone-aware pandas date_range object.
  624. def tz_convert(*args):
  625. return pd.DatetimeIndex.tz_convert(*args).astype(object)
  626. _test_date2num_dst(pd.date_range, tz_convert)
  627. def _test_rrulewrapper(attach_tz, get_tz):
  628. SYD = get_tz('Australia/Sydney')
  629. dtstart = attach_tz(datetime.datetime(2017, 4, 1, 0), SYD)
  630. dtend = attach_tz(datetime.datetime(2017, 4, 4, 0), SYD)
  631. rule = mdates.rrulewrapper(freq=dateutil.rrule.DAILY, dtstart=dtstart)
  632. act = rule.between(dtstart, dtend)
  633. exp = [datetime.datetime(2017, 4, 1, 13, tzinfo=dateutil.tz.tzutc()),
  634. datetime.datetime(2017, 4, 2, 14, tzinfo=dateutil.tz.tzutc())]
  635. assert act == exp
  636. def test_rrulewrapper():
  637. def attach_tz(dt, zi):
  638. return dt.replace(tzinfo=zi)
  639. _test_rrulewrapper(attach_tz, dateutil.tz.gettz)
  640. @pytest.mark.pytz
  641. def test_rrulewrapper_pytz():
  642. # Test to make sure pytz zones are supported in rrules
  643. pytz = pytest.importorskip("pytz")
  644. def attach_tz(dt, zi):
  645. return zi.localize(dt)
  646. _test_rrulewrapper(attach_tz, pytz.timezone)
  647. @pytest.mark.pytz
  648. def test_yearlocator_pytz():
  649. pytz = pytest.importorskip("pytz")
  650. tz = pytz.timezone('America/New_York')
  651. x = [tz.localize(datetime.datetime(2010, 1, 1))
  652. + datetime.timedelta(i) for i in range(2000)]
  653. locator = mdates.AutoDateLocator(interval_multiples=True, tz=tz)
  654. locator.create_dummy_axis()
  655. locator.set_view_interval(mdates.date2num(x[0])-1.0,
  656. mdates.date2num(x[-1])+1.0)
  657. np.testing.assert_allclose([733408.208333, 733773.208333, 734138.208333,
  658. 734503.208333, 734869.208333,
  659. 735234.208333, 735599.208333], locator())
  660. expected = ['2009-01-01 00:00:00-05:00',
  661. '2010-01-01 00:00:00-05:00', '2011-01-01 00:00:00-05:00',
  662. '2012-01-01 00:00:00-05:00', '2013-01-01 00:00:00-05:00',
  663. '2014-01-01 00:00:00-05:00', '2015-01-01 00:00:00-05:00']
  664. st = list(map(str, mdates.num2date(locator(), tz=tz)))
  665. assert st == expected
  666. def test_DayLocator():
  667. with pytest.raises(ValueError):
  668. mdates.DayLocator(interval=-1)
  669. with pytest.raises(ValueError):
  670. mdates.DayLocator(interval=-1.5)
  671. with pytest.raises(ValueError):
  672. mdates.DayLocator(interval=0)
  673. with pytest.raises(ValueError):
  674. mdates.DayLocator(interval=1.3)
  675. mdates.DayLocator(interval=1.0)
  676. def test_tz_utc():
  677. dt = datetime.datetime(1970, 1, 1, tzinfo=mdates.UTC)
  678. dt.tzname()
  679. @pytest.mark.parametrize("x, tdelta",
  680. [(1, datetime.timedelta(days=1)),
  681. ([1, 1.5], [datetime.timedelta(days=1),
  682. datetime.timedelta(days=1.5)])])
  683. def test_num2timedelta(x, tdelta):
  684. dt = mdates.num2timedelta(x)
  685. assert dt == tdelta
  686. def test_datetime64_in_list():
  687. dt = [np.datetime64('2000-01-01'), np.datetime64('2001-01-01')]
  688. dn = mdates.date2num(dt)
  689. np.testing.assert_equal(dn, [730120., 730486.])