test_dates.py 55 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411
  1. import datetime
  2. import dateutil.tz
  3. import dateutil.rrule
  4. import functools
  5. import numpy as np
  6. import pytest
  7. import matplotlib as mpl
  8. from matplotlib import rc_context, style
  9. import matplotlib.dates as mdates
  10. import matplotlib.pyplot as plt
  11. from matplotlib.testing.decorators import image_comparison
  12. import matplotlib.ticker as mticker
  13. def test_date_numpyx():
  14. # test that numpy dates work properly...
  15. base = datetime.datetime(2017, 1, 1)
  16. time = [base + datetime.timedelta(days=x) for x in range(0, 3)]
  17. timenp = np.array(time, dtype='datetime64[ns]')
  18. data = np.array([0., 2., 1.])
  19. fig = plt.figure(figsize=(10, 2))
  20. ax = fig.add_subplot(1, 1, 1)
  21. h, = ax.plot(time, data)
  22. hnp, = ax.plot(timenp, data)
  23. np.testing.assert_equal(h.get_xdata(orig=False), hnp.get_xdata(orig=False))
  24. fig = plt.figure(figsize=(10, 2))
  25. ax = fig.add_subplot(1, 1, 1)
  26. h, = ax.plot(data, time)
  27. hnp, = ax.plot(data, timenp)
  28. np.testing.assert_equal(h.get_ydata(orig=False), hnp.get_ydata(orig=False))
  29. @pytest.mark.parametrize('t0', [datetime.datetime(2017, 1, 1, 0, 1, 1),
  30. [datetime.datetime(2017, 1, 1, 0, 1, 1),
  31. datetime.datetime(2017, 1, 1, 1, 1, 1)],
  32. [[datetime.datetime(2017, 1, 1, 0, 1, 1),
  33. datetime.datetime(2017, 1, 1, 1, 1, 1)],
  34. [datetime.datetime(2017, 1, 1, 2, 1, 1),
  35. datetime.datetime(2017, 1, 1, 3, 1, 1)]]])
  36. @pytest.mark.parametrize('dtype', ['datetime64[s]',
  37. 'datetime64[us]',
  38. 'datetime64[ms]',
  39. 'datetime64[ns]'])
  40. def test_date_date2num_numpy(t0, dtype):
  41. time = mdates.date2num(t0)
  42. tnp = np.array(t0, dtype=dtype)
  43. nptime = mdates.date2num(tnp)
  44. np.testing.assert_equal(time, nptime)
  45. @pytest.mark.parametrize('dtype', ['datetime64[s]',
  46. 'datetime64[us]',
  47. 'datetime64[ms]',
  48. 'datetime64[ns]'])
  49. def test_date2num_NaT(dtype):
  50. t0 = datetime.datetime(2017, 1, 1, 0, 1, 1)
  51. tmpl = [mdates.date2num(t0), np.nan]
  52. tnp = np.array([t0, 'NaT'], dtype=dtype)
  53. nptime = mdates.date2num(tnp)
  54. np.testing.assert_array_equal(tmpl, nptime)
  55. @pytest.mark.parametrize('units', ['s', 'ms', 'us', 'ns'])
  56. def test_date2num_NaT_scalar(units):
  57. tmpl = mdates.date2num(np.datetime64('NaT', units))
  58. assert np.isnan(tmpl)
  59. def test_date2num_masked():
  60. # Without tzinfo
  61. base = datetime.datetime(2022, 12, 15)
  62. dates = np.ma.array([base + datetime.timedelta(days=(2 * i))
  63. for i in range(7)], mask=[0, 1, 1, 0, 0, 0, 1])
  64. npdates = mdates.date2num(dates)
  65. np.testing.assert_array_equal(np.ma.getmask(npdates),
  66. (False, True, True, False, False, False,
  67. True))
  68. # With tzinfo
  69. base = datetime.datetime(2022, 12, 15, tzinfo=mdates.UTC)
  70. dates = np.ma.array([base + datetime.timedelta(days=(2 * i))
  71. for i in range(7)], mask=[0, 1, 1, 0, 0, 0, 1])
  72. npdates = mdates.date2num(dates)
  73. np.testing.assert_array_equal(np.ma.getmask(npdates),
  74. (False, True, True, False, False, False,
  75. True))
  76. def test_date_empty():
  77. # make sure we do the right thing when told to plot dates even
  78. # if no date data has been presented, cf
  79. # http://sourceforge.net/tracker/?func=detail&aid=2850075&group_id=80706&atid=560720
  80. fig, ax = plt.subplots()
  81. ax.xaxis_date()
  82. fig.draw_without_rendering()
  83. np.testing.assert_allclose(ax.get_xlim(),
  84. [mdates.date2num(np.datetime64('1970-01-01')),
  85. mdates.date2num(np.datetime64('1970-01-02'))])
  86. mdates._reset_epoch_test_example()
  87. mdates.set_epoch('0000-12-31')
  88. fig, ax = plt.subplots()
  89. ax.xaxis_date()
  90. fig.draw_without_rendering()
  91. np.testing.assert_allclose(ax.get_xlim(),
  92. [mdates.date2num(np.datetime64('1970-01-01')),
  93. mdates.date2num(np.datetime64('1970-01-02'))])
  94. mdates._reset_epoch_test_example()
  95. def test_date_not_empty():
  96. fig = plt.figure()
  97. ax = fig.add_subplot()
  98. ax.plot([50, 70], [1, 2])
  99. ax.xaxis.axis_date()
  100. np.testing.assert_allclose(ax.get_xlim(), [50, 70])
  101. def test_axhline():
  102. # make sure that axhline doesn't set the xlimits...
  103. fig, ax = plt.subplots()
  104. ax.axhline(1.5)
  105. ax.plot([np.datetime64('2016-01-01'), np.datetime64('2016-01-02')], [1, 2])
  106. np.testing.assert_allclose(ax.get_xlim(),
  107. [mdates.date2num(np.datetime64('2016-01-01')),
  108. mdates.date2num(np.datetime64('2016-01-02'))])
  109. mdates._reset_epoch_test_example()
  110. mdates.set_epoch('0000-12-31')
  111. fig, ax = plt.subplots()
  112. ax.axhline(1.5)
  113. ax.plot([np.datetime64('2016-01-01'), np.datetime64('2016-01-02')], [1, 2])
  114. np.testing.assert_allclose(ax.get_xlim(),
  115. [mdates.date2num(np.datetime64('2016-01-01')),
  116. mdates.date2num(np.datetime64('2016-01-02'))])
  117. mdates._reset_epoch_test_example()
  118. @image_comparison(['date_axhspan.png'])
  119. def test_date_axhspan():
  120. # test axhspan with date inputs
  121. t0 = datetime.datetime(2009, 1, 20)
  122. tf = datetime.datetime(2009, 1, 21)
  123. fig, ax = plt.subplots()
  124. ax.axhspan(t0, tf, facecolor="blue", alpha=0.25)
  125. ax.set_ylim(t0 - datetime.timedelta(days=5),
  126. tf + datetime.timedelta(days=5))
  127. fig.subplots_adjust(left=0.25)
  128. @image_comparison(['date_axvspan.png'])
  129. def test_date_axvspan():
  130. # test axvspan with date inputs
  131. t0 = datetime.datetime(2000, 1, 20)
  132. tf = datetime.datetime(2010, 1, 21)
  133. fig, ax = plt.subplots()
  134. ax.axvspan(t0, tf, facecolor="blue", alpha=0.25)
  135. ax.set_xlim(t0 - datetime.timedelta(days=720),
  136. tf + datetime.timedelta(days=720))
  137. fig.autofmt_xdate()
  138. @image_comparison(['date_axhline.png'])
  139. def test_date_axhline():
  140. # test axhline with date inputs
  141. t0 = datetime.datetime(2009, 1, 20)
  142. tf = datetime.datetime(2009, 1, 31)
  143. fig, ax = plt.subplots()
  144. ax.axhline(t0, color="blue", lw=3)
  145. ax.set_ylim(t0 - datetime.timedelta(days=5),
  146. tf + datetime.timedelta(days=5))
  147. fig.subplots_adjust(left=0.25)
  148. @image_comparison(['date_axvline.png'])
  149. def test_date_axvline():
  150. # test axvline with date inputs
  151. t0 = datetime.datetime(2000, 1, 20)
  152. tf = datetime.datetime(2000, 1, 21)
  153. fig, ax = plt.subplots()
  154. ax.axvline(t0, color="red", lw=3)
  155. ax.set_xlim(t0 - datetime.timedelta(days=5),
  156. tf + datetime.timedelta(days=5))
  157. fig.autofmt_xdate()
  158. def test_too_many_date_ticks(caplog):
  159. # Attempt to test SF 2715172, see
  160. # https://sourceforge.net/tracker/?func=detail&aid=2715172&group_id=80706&atid=560720
  161. # setting equal datetimes triggers and expander call in
  162. # transforms.nonsingular which results in too many ticks in the
  163. # DayLocator. This should emit a log at WARNING level.
  164. caplog.set_level("WARNING")
  165. t0 = datetime.datetime(2000, 1, 20)
  166. tf = datetime.datetime(2000, 1, 20)
  167. fig, ax = plt.subplots()
  168. with pytest.warns(UserWarning) as rec:
  169. ax.set_xlim((t0, tf), auto=True)
  170. assert len(rec) == 1
  171. assert ('Attempting to set identical low and high xlims'
  172. in str(rec[0].message))
  173. ax.plot([], [])
  174. ax.xaxis.set_major_locator(mdates.DayLocator())
  175. v = ax.xaxis.get_major_locator()()
  176. assert len(v) > 1000
  177. # The warning is emitted multiple times because the major locator is also
  178. # called both when placing the minor ticks (for overstriking detection) and
  179. # during tick label positioning.
  180. assert caplog.records and all(
  181. record.name == "matplotlib.ticker" and record.levelname == "WARNING"
  182. for record in caplog.records)
  183. assert len(caplog.records) > 0
  184. def _new_epoch_decorator(thefunc):
  185. @functools.wraps(thefunc)
  186. def wrapper():
  187. mdates._reset_epoch_test_example()
  188. mdates.set_epoch('2000-01-01')
  189. thefunc()
  190. mdates._reset_epoch_test_example()
  191. return wrapper
  192. @image_comparison(['RRuleLocator_bounds.png'])
  193. def test_RRuleLocator():
  194. import matplotlib.testing.jpl_units as units
  195. units.register()
  196. # This will cause the RRuleLocator to go out of bounds when it tries
  197. # to add padding to the limits, so we make sure it caps at the correct
  198. # boundary values.
  199. t0 = datetime.datetime(1000, 1, 1)
  200. tf = datetime.datetime(6000, 1, 1)
  201. fig = plt.figure()
  202. ax = plt.subplot()
  203. ax.set_autoscale_on(True)
  204. ax.plot([t0, tf], [0.0, 1.0], marker='o')
  205. rrule = mdates.rrulewrapper(dateutil.rrule.YEARLY, interval=500)
  206. locator = mdates.RRuleLocator(rrule)
  207. ax.xaxis.set_major_locator(locator)
  208. ax.xaxis.set_major_formatter(mdates.AutoDateFormatter(locator))
  209. ax.autoscale_view()
  210. fig.autofmt_xdate()
  211. def test_RRuleLocator_dayrange():
  212. loc = mdates.DayLocator()
  213. x1 = datetime.datetime(year=1, month=1, day=1, tzinfo=mdates.UTC)
  214. y1 = datetime.datetime(year=1, month=1, day=16, tzinfo=mdates.UTC)
  215. loc.tick_values(x1, y1)
  216. # On success, no overflow error shall be thrown
  217. def test_RRuleLocator_close_minmax():
  218. # if d1 and d2 are very close together, rrule cannot create
  219. # reasonable tick intervals; ensure that this is handled properly
  220. rrule = mdates.rrulewrapper(dateutil.rrule.SECONDLY, interval=5)
  221. loc = mdates.RRuleLocator(rrule)
  222. d1 = datetime.datetime(year=2020, month=1, day=1)
  223. d2 = datetime.datetime(year=2020, month=1, day=1, microsecond=1)
  224. expected = ['2020-01-01 00:00:00+00:00',
  225. '2020-01-01 00:00:00.000001+00:00']
  226. assert list(map(str, mdates.num2date(loc.tick_values(d1, d2)))) == expected
  227. @image_comparison(['DateFormatter_fractionalSeconds.png'])
  228. def test_DateFormatter():
  229. import matplotlib.testing.jpl_units as units
  230. units.register()
  231. # Lets make sure that DateFormatter will allow us to have tick marks
  232. # at intervals of fractional seconds.
  233. t0 = datetime.datetime(2001, 1, 1, 0, 0, 0)
  234. tf = datetime.datetime(2001, 1, 1, 0, 0, 1)
  235. fig = plt.figure()
  236. ax = plt.subplot()
  237. ax.set_autoscale_on(True)
  238. ax.plot([t0, tf], [0.0, 1.0], marker='o')
  239. # rrule = mpldates.rrulewrapper( dateutil.rrule.YEARLY, interval=500 )
  240. # locator = mpldates.RRuleLocator( rrule )
  241. # ax.xaxis.set_major_locator( locator )
  242. # ax.xaxis.set_major_formatter( mpldates.AutoDateFormatter(locator) )
  243. ax.autoscale_view()
  244. fig.autofmt_xdate()
  245. def test_locator_set_formatter():
  246. """
  247. Test if setting the locator only will update the AutoDateFormatter to use
  248. the new locator.
  249. """
  250. plt.rcParams["date.autoformatter.minute"] = "%d %H:%M"
  251. t = [datetime.datetime(2018, 9, 30, 8, 0),
  252. datetime.datetime(2018, 9, 30, 8, 59),
  253. datetime.datetime(2018, 9, 30, 10, 30)]
  254. x = [2, 3, 1]
  255. fig, ax = plt.subplots()
  256. ax.plot(t, x)
  257. ax.xaxis.set_major_locator(mdates.MinuteLocator((0, 30)))
  258. fig.canvas.draw()
  259. ticklabels = [tl.get_text() for tl in ax.get_xticklabels()]
  260. expected = ['30 08:00', '30 08:30', '30 09:00',
  261. '30 09:30', '30 10:00', '30 10:30']
  262. assert ticklabels == expected
  263. ax.xaxis.set_major_locator(mticker.NullLocator())
  264. ax.xaxis.set_minor_locator(mdates.MinuteLocator((5, 55)))
  265. decoy_loc = mdates.MinuteLocator((12, 27))
  266. ax.xaxis.set_minor_formatter(mdates.AutoDateFormatter(decoy_loc))
  267. ax.xaxis.set_minor_locator(mdates.MinuteLocator((15, 45)))
  268. fig.canvas.draw()
  269. ticklabels = [tl.get_text() for tl in ax.get_xticklabels(which="minor")]
  270. expected = ['30 08:15', '30 08:45', '30 09:15', '30 09:45', '30 10:15']
  271. assert ticklabels == expected
  272. def test_date_formatter_callable():
  273. class _Locator:
  274. def _get_unit(self): return -11
  275. def callable_formatting_function(dates, _):
  276. return [dt.strftime('%d-%m//%Y') for dt in dates]
  277. formatter = mdates.AutoDateFormatter(_Locator())
  278. formatter.scaled[-10] = callable_formatting_function
  279. assert formatter([datetime.datetime(2014, 12, 25)]) == ['25-12//2014']
  280. @pytest.mark.parametrize('delta, expected', [
  281. (datetime.timedelta(weeks=52 * 200),
  282. [r'$\mathdefault{%d}$' % year for year in range(1990, 2171, 20)]),
  283. (datetime.timedelta(days=30),
  284. [r'$\mathdefault{1990{-}01{-}%02d}$' % day for day in range(1, 32, 3)]),
  285. (datetime.timedelta(hours=20),
  286. [r'$\mathdefault{01{-}01\;%02d}$' % hour for hour in range(0, 21, 2)]),
  287. (datetime.timedelta(minutes=10),
  288. [r'$\mathdefault{01\;00{:}%02d}$' % minu for minu in range(0, 11)]),
  289. ])
  290. def test_date_formatter_usetex(delta, expected):
  291. style.use("default")
  292. d1 = datetime.datetime(1990, 1, 1)
  293. d2 = d1 + delta
  294. locator = mdates.AutoDateLocator(interval_multiples=False)
  295. locator.create_dummy_axis()
  296. locator.axis.set_view_interval(mdates.date2num(d1), mdates.date2num(d2))
  297. formatter = mdates.AutoDateFormatter(locator, usetex=True)
  298. assert [formatter(loc) for loc in locator()] == expected
  299. def test_drange():
  300. """
  301. This test should check if drange works as expected, and if all the
  302. rounding errors are fixed
  303. """
  304. start = datetime.datetime(2011, 1, 1, tzinfo=mdates.UTC)
  305. end = datetime.datetime(2011, 1, 2, tzinfo=mdates.UTC)
  306. delta = datetime.timedelta(hours=1)
  307. # We expect 24 values in drange(start, end, delta), because drange returns
  308. # dates from an half open interval [start, end)
  309. assert len(mdates.drange(start, end, delta)) == 24
  310. # Same if interval ends slightly earlier
  311. end = end - datetime.timedelta(microseconds=1)
  312. assert len(mdates.drange(start, end, delta)) == 24
  313. # if end is a little bit later, we expect the range to contain one element
  314. # more
  315. end = end + datetime.timedelta(microseconds=2)
  316. assert len(mdates.drange(start, end, delta)) == 25
  317. # reset end
  318. end = datetime.datetime(2011, 1, 2, tzinfo=mdates.UTC)
  319. # and tst drange with "complicated" floats:
  320. # 4 hours = 1/6 day, this is an "dangerous" float
  321. delta = datetime.timedelta(hours=4)
  322. daterange = mdates.drange(start, end, delta)
  323. assert len(daterange) == 6
  324. assert mdates.num2date(daterange[-1]) == (end - delta)
  325. @_new_epoch_decorator
  326. def test_auto_date_locator():
  327. def _create_auto_date_locator(date1, date2):
  328. locator = mdates.AutoDateLocator(interval_multiples=False)
  329. locator.create_dummy_axis()
  330. locator.axis.set_view_interval(*mdates.date2num([date1, date2]))
  331. return locator
  332. d1 = datetime.datetime(1990, 1, 1)
  333. results = ([datetime.timedelta(weeks=52 * 200),
  334. ['1990-01-01 00:00:00+00:00', '2010-01-01 00:00:00+00:00',
  335. '2030-01-01 00:00:00+00:00', '2050-01-01 00:00:00+00:00',
  336. '2070-01-01 00:00:00+00:00', '2090-01-01 00:00:00+00:00',
  337. '2110-01-01 00:00:00+00:00', '2130-01-01 00:00:00+00:00',
  338. '2150-01-01 00:00:00+00:00', '2170-01-01 00:00:00+00:00']
  339. ],
  340. [datetime.timedelta(weeks=52),
  341. ['1990-01-01 00:00:00+00:00', '1990-02-01 00:00:00+00:00',
  342. '1990-03-01 00:00:00+00:00', '1990-04-01 00:00:00+00:00',
  343. '1990-05-01 00:00:00+00:00', '1990-06-01 00:00:00+00:00',
  344. '1990-07-01 00:00:00+00:00', '1990-08-01 00:00:00+00:00',
  345. '1990-09-01 00:00:00+00:00', '1990-10-01 00:00:00+00:00',
  346. '1990-11-01 00:00:00+00:00', '1990-12-01 00:00:00+00:00']
  347. ],
  348. [datetime.timedelta(days=141),
  349. ['1990-01-05 00:00:00+00:00', '1990-01-26 00:00:00+00:00',
  350. '1990-02-16 00:00:00+00:00', '1990-03-09 00:00:00+00:00',
  351. '1990-03-30 00:00:00+00:00', '1990-04-20 00:00:00+00:00',
  352. '1990-05-11 00:00:00+00:00']
  353. ],
  354. [datetime.timedelta(days=40),
  355. ['1990-01-03 00:00:00+00:00', '1990-01-10 00:00:00+00:00',
  356. '1990-01-17 00:00:00+00:00', '1990-01-24 00:00:00+00:00',
  357. '1990-01-31 00:00:00+00:00', '1990-02-07 00:00:00+00:00']
  358. ],
  359. [datetime.timedelta(hours=40),
  360. ['1990-01-01 00:00:00+00:00', '1990-01-01 04:00:00+00:00',
  361. '1990-01-01 08:00:00+00:00', '1990-01-01 12:00:00+00:00',
  362. '1990-01-01 16:00:00+00:00', '1990-01-01 20:00:00+00:00',
  363. '1990-01-02 00:00:00+00:00', '1990-01-02 04:00:00+00:00',
  364. '1990-01-02 08:00:00+00:00', '1990-01-02 12:00:00+00:00',
  365. '1990-01-02 16:00:00+00:00']
  366. ],
  367. [datetime.timedelta(minutes=20),
  368. ['1990-01-01 00:00:00+00:00', '1990-01-01 00:05:00+00:00',
  369. '1990-01-01 00:10:00+00:00', '1990-01-01 00:15:00+00:00',
  370. '1990-01-01 00:20:00+00:00']
  371. ],
  372. [datetime.timedelta(seconds=40),
  373. ['1990-01-01 00:00:00+00:00', '1990-01-01 00:00:05+00:00',
  374. '1990-01-01 00:00:10+00:00', '1990-01-01 00:00:15+00:00',
  375. '1990-01-01 00:00:20+00:00', '1990-01-01 00:00:25+00:00',
  376. '1990-01-01 00:00:30+00:00', '1990-01-01 00:00:35+00:00',
  377. '1990-01-01 00:00:40+00:00']
  378. ],
  379. [datetime.timedelta(microseconds=1500),
  380. ['1989-12-31 23:59:59.999500+00:00',
  381. '1990-01-01 00:00:00+00:00',
  382. '1990-01-01 00:00:00.000500+00:00',
  383. '1990-01-01 00:00:00.001000+00:00',
  384. '1990-01-01 00:00:00.001500+00:00',
  385. '1990-01-01 00:00:00.002000+00:00']
  386. ],
  387. )
  388. for t_delta, expected in results:
  389. d2 = d1 + t_delta
  390. locator = _create_auto_date_locator(d1, d2)
  391. assert list(map(str, mdates.num2date(locator()))) == expected
  392. locator = mdates.AutoDateLocator(interval_multiples=False)
  393. assert locator.maxticks == {0: 11, 1: 12, 3: 11, 4: 12, 5: 11, 6: 11, 7: 8}
  394. locator = mdates.AutoDateLocator(maxticks={dateutil.rrule.MONTHLY: 5})
  395. assert locator.maxticks == {0: 11, 1: 5, 3: 11, 4: 12, 5: 11, 6: 11, 7: 8}
  396. locator = mdates.AutoDateLocator(maxticks=5)
  397. assert locator.maxticks == {0: 5, 1: 5, 3: 5, 4: 5, 5: 5, 6: 5, 7: 5}
  398. @_new_epoch_decorator
  399. def test_auto_date_locator_intmult():
  400. def _create_auto_date_locator(date1, date2):
  401. locator = mdates.AutoDateLocator(interval_multiples=True)
  402. locator.create_dummy_axis()
  403. locator.axis.set_view_interval(*mdates.date2num([date1, date2]))
  404. return locator
  405. results = ([datetime.timedelta(weeks=52 * 200),
  406. ['1980-01-01 00:00:00+00:00', '2000-01-01 00:00:00+00:00',
  407. '2020-01-01 00:00:00+00:00', '2040-01-01 00:00:00+00:00',
  408. '2060-01-01 00:00:00+00:00', '2080-01-01 00:00:00+00:00',
  409. '2100-01-01 00:00:00+00:00', '2120-01-01 00:00:00+00:00',
  410. '2140-01-01 00:00:00+00:00', '2160-01-01 00:00:00+00:00',
  411. '2180-01-01 00:00:00+00:00', '2200-01-01 00:00:00+00:00']
  412. ],
  413. [datetime.timedelta(weeks=52),
  414. ['1997-01-01 00:00:00+00:00', '1997-02-01 00:00:00+00:00',
  415. '1997-03-01 00:00:00+00:00', '1997-04-01 00:00:00+00:00',
  416. '1997-05-01 00:00:00+00:00', '1997-06-01 00:00:00+00:00',
  417. '1997-07-01 00:00:00+00:00', '1997-08-01 00:00:00+00:00',
  418. '1997-09-01 00:00:00+00:00', '1997-10-01 00:00:00+00:00',
  419. '1997-11-01 00:00:00+00:00', '1997-12-01 00:00:00+00:00']
  420. ],
  421. [datetime.timedelta(days=141),
  422. ['1997-01-01 00:00:00+00:00', '1997-01-15 00:00:00+00:00',
  423. '1997-02-01 00:00:00+00:00', '1997-02-15 00:00:00+00:00',
  424. '1997-03-01 00:00:00+00:00', '1997-03-15 00:00:00+00:00',
  425. '1997-04-01 00:00:00+00:00', '1997-04-15 00:00:00+00:00',
  426. '1997-05-01 00:00:00+00:00', '1997-05-15 00:00:00+00:00']
  427. ],
  428. [datetime.timedelta(days=40),
  429. ['1997-01-01 00:00:00+00:00', '1997-01-05 00:00:00+00:00',
  430. '1997-01-09 00:00:00+00:00', '1997-01-13 00:00:00+00:00',
  431. '1997-01-17 00:00:00+00:00', '1997-01-21 00:00:00+00:00',
  432. '1997-01-25 00:00:00+00:00', '1997-01-29 00:00:00+00:00',
  433. '1997-02-01 00:00:00+00:00', '1997-02-05 00:00:00+00:00',
  434. '1997-02-09 00:00:00+00:00']
  435. ],
  436. [datetime.timedelta(hours=40),
  437. ['1997-01-01 00:00:00+00:00', '1997-01-01 04:00:00+00:00',
  438. '1997-01-01 08:00:00+00:00', '1997-01-01 12:00:00+00:00',
  439. '1997-01-01 16:00:00+00:00', '1997-01-01 20:00:00+00:00',
  440. '1997-01-02 00:00:00+00:00', '1997-01-02 04:00:00+00:00',
  441. '1997-01-02 08:00:00+00:00', '1997-01-02 12:00:00+00:00',
  442. '1997-01-02 16:00:00+00:00']
  443. ],
  444. [datetime.timedelta(minutes=20),
  445. ['1997-01-01 00:00:00+00:00', '1997-01-01 00:05:00+00:00',
  446. '1997-01-01 00:10:00+00:00', '1997-01-01 00:15:00+00:00',
  447. '1997-01-01 00:20:00+00:00']
  448. ],
  449. [datetime.timedelta(seconds=40),
  450. ['1997-01-01 00:00:00+00:00', '1997-01-01 00:00:05+00:00',
  451. '1997-01-01 00:00:10+00:00', '1997-01-01 00:00:15+00:00',
  452. '1997-01-01 00:00:20+00:00', '1997-01-01 00:00:25+00:00',
  453. '1997-01-01 00:00:30+00:00', '1997-01-01 00:00:35+00:00',
  454. '1997-01-01 00:00:40+00:00']
  455. ],
  456. [datetime.timedelta(microseconds=1500),
  457. ['1996-12-31 23:59:59.999500+00:00',
  458. '1997-01-01 00:00:00+00:00',
  459. '1997-01-01 00:00:00.000500+00:00',
  460. '1997-01-01 00:00:00.001000+00:00',
  461. '1997-01-01 00:00:00.001500+00:00',
  462. '1997-01-01 00:00:00.002000+00:00']
  463. ],
  464. )
  465. d1 = datetime.datetime(1997, 1, 1)
  466. for t_delta, expected in results:
  467. d2 = d1 + t_delta
  468. locator = _create_auto_date_locator(d1, d2)
  469. assert list(map(str, mdates.num2date(locator()))) == expected
  470. def test_concise_formatter_subsecond():
  471. locator = mdates.AutoDateLocator(interval_multiples=True)
  472. formatter = mdates.ConciseDateFormatter(locator)
  473. year_1996 = 9861.0
  474. strings = formatter.format_ticks([
  475. year_1996,
  476. year_1996 + 500 / mdates.MUSECONDS_PER_DAY,
  477. year_1996 + 900 / mdates.MUSECONDS_PER_DAY])
  478. assert strings == ['00:00', '00.0005', '00.0009']
  479. def test_concise_formatter():
  480. def _create_auto_date_locator(date1, date2):
  481. fig, ax = plt.subplots()
  482. locator = mdates.AutoDateLocator(interval_multiples=True)
  483. formatter = mdates.ConciseDateFormatter(locator)
  484. ax.yaxis.set_major_locator(locator)
  485. ax.yaxis.set_major_formatter(formatter)
  486. ax.set_ylim(date1, date2)
  487. fig.canvas.draw()
  488. sts = [st.get_text() for st in ax.get_yticklabels()]
  489. return sts
  490. d1 = datetime.datetime(1997, 1, 1)
  491. results = ([datetime.timedelta(weeks=52 * 200),
  492. [str(t) for t in range(1980, 2201, 20)]
  493. ],
  494. [datetime.timedelta(weeks=52),
  495. ['1997', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug',
  496. 'Sep', 'Oct', 'Nov', 'Dec']
  497. ],
  498. [datetime.timedelta(days=141),
  499. ['Jan', '15', 'Feb', '15', 'Mar', '15', 'Apr', '15',
  500. 'May', '15']
  501. ],
  502. [datetime.timedelta(days=40),
  503. ['Jan', '05', '09', '13', '17', '21', '25', '29', 'Feb',
  504. '05', '09']
  505. ],
  506. [datetime.timedelta(hours=40),
  507. ['Jan-01', '04:00', '08:00', '12:00', '16:00', '20:00',
  508. 'Jan-02', '04:00', '08:00', '12:00', '16:00']
  509. ],
  510. [datetime.timedelta(minutes=20),
  511. ['00:00', '00:05', '00:10', '00:15', '00:20']
  512. ],
  513. [datetime.timedelta(seconds=40),
  514. ['00:00', '05', '10', '15', '20', '25', '30', '35', '40']
  515. ],
  516. [datetime.timedelta(seconds=2),
  517. ['59.5', '00:00', '00.5', '01.0', '01.5', '02.0', '02.5']
  518. ],
  519. )
  520. for t_delta, expected in results:
  521. d2 = d1 + t_delta
  522. strings = _create_auto_date_locator(d1, d2)
  523. assert strings == expected
  524. @pytest.mark.parametrize('t_delta, expected', [
  525. (datetime.timedelta(seconds=0.01), '1997-Jan-01 00:00'),
  526. (datetime.timedelta(minutes=1), '1997-Jan-01 00:01'),
  527. (datetime.timedelta(hours=1), '1997-Jan-01'),
  528. (datetime.timedelta(days=1), '1997-Jan-02'),
  529. (datetime.timedelta(weeks=1), '1997-Jan'),
  530. (datetime.timedelta(weeks=26), ''),
  531. (datetime.timedelta(weeks=520), '')
  532. ])
  533. def test_concise_formatter_show_offset(t_delta, expected):
  534. d1 = datetime.datetime(1997, 1, 1)
  535. d2 = d1 + t_delta
  536. fig, ax = plt.subplots()
  537. locator = mdates.AutoDateLocator()
  538. formatter = mdates.ConciseDateFormatter(locator)
  539. ax.xaxis.set_major_locator(locator)
  540. ax.xaxis.set_major_formatter(formatter)
  541. ax.plot([d1, d2], [0, 0])
  542. fig.canvas.draw()
  543. assert formatter.get_offset() == expected
  544. def test_concise_converter_stays():
  545. # This test demonstrates problems introduced by gh-23417 (reverted in gh-25278)
  546. # In particular, downstream libraries like Pandas had their designated converters
  547. # overridden by actions like setting xlim (or plotting additional points using
  548. # stdlib/numpy dates and string date representation, which otherwise work fine with
  549. # their date converters)
  550. # While this is a bit of a toy example that would be unusual to see it demonstrates
  551. # the same ideas (namely having a valid converter already applied that is desired)
  552. # without introducing additional subclasses.
  553. # See also discussion at gh-25219 for how Pandas was affected
  554. x = [datetime.datetime(2000, 1, 1), datetime.datetime(2020, 2, 20)]
  555. y = [0, 1]
  556. fig, ax = plt.subplots()
  557. ax.plot(x, y)
  558. # Bypass Switchable date converter
  559. ax.xaxis.converter = conv = mdates.ConciseDateConverter()
  560. assert ax.xaxis.units is None
  561. ax.set_xlim(*x)
  562. assert ax.xaxis.converter == conv
  563. def test_offset_changes():
  564. fig, ax = plt.subplots()
  565. d1 = datetime.datetime(1997, 1, 1)
  566. d2 = d1 + datetime.timedelta(weeks=520)
  567. locator = mdates.AutoDateLocator()
  568. formatter = mdates.ConciseDateFormatter(locator)
  569. ax.xaxis.set_major_locator(locator)
  570. ax.xaxis.set_major_formatter(formatter)
  571. ax.plot([d1, d2], [0, 0])
  572. fig.draw_without_rendering()
  573. assert formatter.get_offset() == ''
  574. ax.set_xlim(d1, d1 + datetime.timedelta(weeks=3))
  575. fig.draw_without_rendering()
  576. assert formatter.get_offset() == '1997-Jan'
  577. ax.set_xlim(d1 + datetime.timedelta(weeks=7),
  578. d1 + datetime.timedelta(weeks=30))
  579. fig.draw_without_rendering()
  580. assert formatter.get_offset() == '1997'
  581. ax.set_xlim(d1, d1 + datetime.timedelta(weeks=520))
  582. fig.draw_without_rendering()
  583. assert formatter.get_offset() == ''
  584. @pytest.mark.parametrize('t_delta, expected', [
  585. (datetime.timedelta(weeks=52 * 200),
  586. ['$\\mathdefault{%d}$' % (t, ) for t in range(1980, 2201, 20)]),
  587. (datetime.timedelta(days=40),
  588. ['Jan', '$\\mathdefault{05}$', '$\\mathdefault{09}$',
  589. '$\\mathdefault{13}$', '$\\mathdefault{17}$', '$\\mathdefault{21}$',
  590. '$\\mathdefault{25}$', '$\\mathdefault{29}$', 'Feb',
  591. '$\\mathdefault{05}$', '$\\mathdefault{09}$']),
  592. (datetime.timedelta(hours=40),
  593. ['Jan$\\mathdefault{{-}01}$', '$\\mathdefault{04{:}00}$',
  594. '$\\mathdefault{08{:}00}$', '$\\mathdefault{12{:}00}$',
  595. '$\\mathdefault{16{:}00}$', '$\\mathdefault{20{:}00}$',
  596. 'Jan$\\mathdefault{{-}02}$', '$\\mathdefault{04{:}00}$',
  597. '$\\mathdefault{08{:}00}$', '$\\mathdefault{12{:}00}$',
  598. '$\\mathdefault{16{:}00}$']),
  599. (datetime.timedelta(seconds=2),
  600. ['$\\mathdefault{59.5}$', '$\\mathdefault{00{:}00}$',
  601. '$\\mathdefault{00.5}$', '$\\mathdefault{01.0}$',
  602. '$\\mathdefault{01.5}$', '$\\mathdefault{02.0}$',
  603. '$\\mathdefault{02.5}$']),
  604. ])
  605. def test_concise_formatter_usetex(t_delta, expected):
  606. d1 = datetime.datetime(1997, 1, 1)
  607. d2 = d1 + t_delta
  608. locator = mdates.AutoDateLocator(interval_multiples=True)
  609. locator.create_dummy_axis()
  610. locator.axis.set_view_interval(mdates.date2num(d1), mdates.date2num(d2))
  611. formatter = mdates.ConciseDateFormatter(locator, usetex=True)
  612. assert formatter.format_ticks(locator()) == expected
  613. def test_concise_formatter_formats():
  614. formats = ['%Y', '%m/%Y', 'day: %d',
  615. '%H hr %M min', '%H hr %M min', '%S.%f sec']
  616. def _create_auto_date_locator(date1, date2):
  617. fig, ax = plt.subplots()
  618. locator = mdates.AutoDateLocator(interval_multiples=True)
  619. formatter = mdates.ConciseDateFormatter(locator, formats=formats)
  620. ax.yaxis.set_major_locator(locator)
  621. ax.yaxis.set_major_formatter(formatter)
  622. ax.set_ylim(date1, date2)
  623. fig.canvas.draw()
  624. sts = [st.get_text() for st in ax.get_yticklabels()]
  625. return sts
  626. d1 = datetime.datetime(1997, 1, 1)
  627. results = (
  628. [datetime.timedelta(weeks=52 * 200), [str(t) for t in range(1980,
  629. 2201, 20)]],
  630. [datetime.timedelta(weeks=52), [
  631. '1997', '02/1997', '03/1997', '04/1997', '05/1997', '06/1997',
  632. '07/1997', '08/1997', '09/1997', '10/1997', '11/1997', '12/1997',
  633. ]],
  634. [datetime.timedelta(days=141), [
  635. '01/1997', 'day: 15', '02/1997', 'day: 15', '03/1997', 'day: 15',
  636. '04/1997', 'day: 15', '05/1997', 'day: 15',
  637. ]],
  638. [datetime.timedelta(days=40), [
  639. '01/1997', 'day: 05', 'day: 09', 'day: 13', 'day: 17', 'day: 21',
  640. 'day: 25', 'day: 29', '02/1997', 'day: 05', 'day: 09',
  641. ]],
  642. [datetime.timedelta(hours=40), [
  643. 'day: 01', '04 hr 00 min', '08 hr 00 min', '12 hr 00 min',
  644. '16 hr 00 min', '20 hr 00 min', 'day: 02', '04 hr 00 min',
  645. '08 hr 00 min', '12 hr 00 min', '16 hr 00 min',
  646. ]],
  647. [datetime.timedelta(minutes=20), ['00 hr 00 min', '00 hr 05 min',
  648. '00 hr 10 min', '00 hr 15 min', '00 hr 20 min']],
  649. [datetime.timedelta(seconds=40), [
  650. '00 hr 00 min', '05.000000 sec', '10.000000 sec',
  651. '15.000000 sec', '20.000000 sec', '25.000000 sec',
  652. '30.000000 sec', '35.000000 sec', '40.000000 sec',
  653. ]],
  654. [datetime.timedelta(seconds=2), [
  655. '59.500000 sec', '00 hr 00 min', '00.500000 sec', '01.000000 sec',
  656. '01.500000 sec', '02.000000 sec', '02.500000 sec',
  657. ]],
  658. )
  659. for t_delta, expected in results:
  660. d2 = d1 + t_delta
  661. strings = _create_auto_date_locator(d1, d2)
  662. assert strings == expected
  663. def test_concise_formatter_zformats():
  664. zero_formats = ['', "'%y", '%B', '%m-%d', '%S', '%S.%f']
  665. def _create_auto_date_locator(date1, date2):
  666. fig, ax = plt.subplots()
  667. locator = mdates.AutoDateLocator(interval_multiples=True)
  668. formatter = mdates.ConciseDateFormatter(
  669. locator, zero_formats=zero_formats)
  670. ax.yaxis.set_major_locator(locator)
  671. ax.yaxis.set_major_formatter(formatter)
  672. ax.set_ylim(date1, date2)
  673. fig.canvas.draw()
  674. sts = [st.get_text() for st in ax.get_yticklabels()]
  675. return sts
  676. d1 = datetime.datetime(1997, 1, 1)
  677. results = ([datetime.timedelta(weeks=52 * 200),
  678. [str(t) for t in range(1980, 2201, 20)]
  679. ],
  680. [datetime.timedelta(weeks=52),
  681. ["'97", 'Feb', 'Mar', 'Apr', 'May', 'Jun',
  682. 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
  683. ],
  684. [datetime.timedelta(days=141),
  685. ['January', '15', 'February', '15', 'March',
  686. '15', 'April', '15', 'May', '15']
  687. ],
  688. [datetime.timedelta(days=40),
  689. ['January', '05', '09', '13', '17', '21',
  690. '25', '29', 'February', '05', '09']
  691. ],
  692. [datetime.timedelta(hours=40),
  693. ['01-01', '04:00', '08:00', '12:00', '16:00', '20:00',
  694. '01-02', '04:00', '08:00', '12:00', '16:00']
  695. ],
  696. [datetime.timedelta(minutes=20),
  697. ['00', '00:05', '00:10', '00:15', '00:20']
  698. ],
  699. [datetime.timedelta(seconds=40),
  700. ['00', '05', '10', '15', '20', '25', '30', '35', '40']
  701. ],
  702. [datetime.timedelta(seconds=2),
  703. ['59.5', '00.0', '00.5', '01.0', '01.5', '02.0', '02.5']
  704. ],
  705. )
  706. for t_delta, expected in results:
  707. d2 = d1 + t_delta
  708. strings = _create_auto_date_locator(d1, d2)
  709. assert strings == expected
  710. def test_concise_formatter_tz():
  711. def _create_auto_date_locator(date1, date2, tz):
  712. fig, ax = plt.subplots()
  713. locator = mdates.AutoDateLocator(interval_multiples=True)
  714. formatter = mdates.ConciseDateFormatter(locator, tz=tz)
  715. ax.yaxis.set_major_locator(locator)
  716. ax.yaxis.set_major_formatter(formatter)
  717. ax.set_ylim(date1, date2)
  718. fig.canvas.draw()
  719. sts = [st.get_text() for st in ax.get_yticklabels()]
  720. return sts, ax.yaxis.get_offset_text().get_text()
  721. d1 = datetime.datetime(1997, 1, 1).replace(tzinfo=datetime.timezone.utc)
  722. results = ([datetime.timedelta(hours=40),
  723. ['03:00', '07:00', '11:00', '15:00', '19:00', '23:00',
  724. '03:00', '07:00', '11:00', '15:00', '19:00'],
  725. "1997-Jan-02"
  726. ],
  727. [datetime.timedelta(minutes=20),
  728. ['03:00', '03:05', '03:10', '03:15', '03:20'],
  729. "1997-Jan-01"
  730. ],
  731. [datetime.timedelta(seconds=40),
  732. ['03:00', '05', '10', '15', '20', '25', '30', '35', '40'],
  733. "1997-Jan-01 03:00"
  734. ],
  735. [datetime.timedelta(seconds=2),
  736. ['59.5', '03:00', '00.5', '01.0', '01.5', '02.0', '02.5'],
  737. "1997-Jan-01 03:00"
  738. ],
  739. )
  740. new_tz = datetime.timezone(datetime.timedelta(hours=3))
  741. for t_delta, expected_strings, expected_offset in results:
  742. d2 = d1 + t_delta
  743. strings, offset = _create_auto_date_locator(d1, d2, new_tz)
  744. assert strings == expected_strings
  745. assert offset == expected_offset
  746. def test_auto_date_locator_intmult_tz():
  747. def _create_auto_date_locator(date1, date2, tz):
  748. locator = mdates.AutoDateLocator(interval_multiples=True, tz=tz)
  749. locator.create_dummy_axis()
  750. locator.axis.set_view_interval(*mdates.date2num([date1, date2]))
  751. return locator
  752. results = ([datetime.timedelta(weeks=52*200),
  753. ['1980-01-01 00:00:00-08:00', '2000-01-01 00:00:00-08:00',
  754. '2020-01-01 00:00:00-08:00', '2040-01-01 00:00:00-08:00',
  755. '2060-01-01 00:00:00-08:00', '2080-01-01 00:00:00-08:00',
  756. '2100-01-01 00:00:00-08:00', '2120-01-01 00:00:00-08:00',
  757. '2140-01-01 00:00:00-08:00', '2160-01-01 00:00:00-08:00',
  758. '2180-01-01 00:00:00-08:00', '2200-01-01 00:00:00-08:00']
  759. ],
  760. [datetime.timedelta(weeks=52),
  761. ['1997-01-01 00:00:00-08:00', '1997-02-01 00:00:00-08:00',
  762. '1997-03-01 00:00:00-08:00', '1997-04-01 00:00:00-08:00',
  763. '1997-05-01 00:00:00-07:00', '1997-06-01 00:00:00-07:00',
  764. '1997-07-01 00:00:00-07:00', '1997-08-01 00:00:00-07:00',
  765. '1997-09-01 00:00:00-07:00', '1997-10-01 00:00:00-07:00',
  766. '1997-11-01 00:00:00-08:00', '1997-12-01 00:00:00-08:00']
  767. ],
  768. [datetime.timedelta(days=141),
  769. ['1997-01-01 00:00:00-08:00', '1997-01-15 00:00:00-08:00',
  770. '1997-02-01 00:00:00-08:00', '1997-02-15 00:00:00-08:00',
  771. '1997-03-01 00:00:00-08:00', '1997-03-15 00:00:00-08:00',
  772. '1997-04-01 00:00:00-08:00', '1997-04-15 00:00:00-07:00',
  773. '1997-05-01 00:00:00-07:00', '1997-05-15 00:00:00-07:00']
  774. ],
  775. [datetime.timedelta(days=40),
  776. ['1997-01-01 00:00:00-08:00', '1997-01-05 00:00:00-08:00',
  777. '1997-01-09 00:00:00-08:00', '1997-01-13 00:00:00-08:00',
  778. '1997-01-17 00:00:00-08:00', '1997-01-21 00:00:00-08:00',
  779. '1997-01-25 00:00:00-08:00', '1997-01-29 00:00:00-08:00',
  780. '1997-02-01 00:00:00-08:00', '1997-02-05 00:00:00-08:00',
  781. '1997-02-09 00:00:00-08:00']
  782. ],
  783. [datetime.timedelta(hours=40),
  784. ['1997-01-01 00:00:00-08:00', '1997-01-01 04:00:00-08:00',
  785. '1997-01-01 08:00:00-08:00', '1997-01-01 12:00:00-08:00',
  786. '1997-01-01 16:00:00-08:00', '1997-01-01 20:00:00-08:00',
  787. '1997-01-02 00:00:00-08:00', '1997-01-02 04:00:00-08:00',
  788. '1997-01-02 08:00:00-08:00', '1997-01-02 12:00:00-08:00',
  789. '1997-01-02 16:00:00-08:00']
  790. ],
  791. [datetime.timedelta(minutes=20),
  792. ['1997-01-01 00:00:00-08:00', '1997-01-01 00:05:00-08:00',
  793. '1997-01-01 00:10:00-08:00', '1997-01-01 00:15:00-08:00',
  794. '1997-01-01 00:20:00-08:00']
  795. ],
  796. [datetime.timedelta(seconds=40),
  797. ['1997-01-01 00:00:00-08:00', '1997-01-01 00:00:05-08:00',
  798. '1997-01-01 00:00:10-08:00', '1997-01-01 00:00:15-08:00',
  799. '1997-01-01 00:00:20-08:00', '1997-01-01 00:00:25-08:00',
  800. '1997-01-01 00:00:30-08:00', '1997-01-01 00:00:35-08:00',
  801. '1997-01-01 00:00:40-08:00']
  802. ]
  803. )
  804. tz = dateutil.tz.gettz('Canada/Pacific')
  805. d1 = datetime.datetime(1997, 1, 1, tzinfo=tz)
  806. for t_delta, expected in results:
  807. with rc_context({'_internal.classic_mode': False}):
  808. d2 = d1 + t_delta
  809. locator = _create_auto_date_locator(d1, d2, tz)
  810. st = list(map(str, mdates.num2date(locator(), tz=tz)))
  811. assert st == expected
  812. @image_comparison(['date_inverted_limit.png'])
  813. def test_date_inverted_limit():
  814. # test ax hline with date inputs
  815. t0 = datetime.datetime(2009, 1, 20)
  816. tf = datetime.datetime(2009, 1, 31)
  817. fig, ax = plt.subplots()
  818. ax.axhline(t0, color="blue", lw=3)
  819. ax.set_ylim(t0 - datetime.timedelta(days=5),
  820. tf + datetime.timedelta(days=5))
  821. ax.invert_yaxis()
  822. fig.subplots_adjust(left=0.25)
  823. def _test_date2num_dst(date_range, tz_convert):
  824. # Timezones
  825. BRUSSELS = dateutil.tz.gettz('Europe/Brussels')
  826. UTC = mdates.UTC
  827. # Create a list of timezone-aware datetime objects in UTC
  828. # Interval is 0b0.0000011 days, to prevent float rounding issues
  829. dtstart = datetime.datetime(2014, 3, 30, 0, 0, tzinfo=UTC)
  830. interval = datetime.timedelta(minutes=33, seconds=45)
  831. interval_days = interval.seconds / 86400
  832. N = 8
  833. dt_utc = date_range(start=dtstart, freq=interval, periods=N)
  834. dt_bxl = tz_convert(dt_utc, BRUSSELS)
  835. t0 = 735322.0 + mdates.date2num(np.datetime64('0000-12-31'))
  836. expected_ordinalf = [t0 + (i * interval_days) for i in range(N)]
  837. actual_ordinalf = list(mdates.date2num(dt_bxl))
  838. assert actual_ordinalf == expected_ordinalf
  839. def test_date2num_dst():
  840. # Test for github issue #3896, but in date2num around DST transitions
  841. # with a timezone-aware pandas date_range object.
  842. class dt_tzaware(datetime.datetime):
  843. """
  844. This bug specifically occurs because of the normalization behavior of
  845. pandas Timestamp objects, so in order to replicate it, we need a
  846. datetime-like object that applies timezone normalization after
  847. subtraction.
  848. """
  849. def __sub__(self, other):
  850. r = super().__sub__(other)
  851. tzinfo = getattr(r, 'tzinfo', None)
  852. if tzinfo is not None:
  853. localizer = getattr(tzinfo, 'normalize', None)
  854. if localizer is not None:
  855. r = tzinfo.normalize(r)
  856. if isinstance(r, datetime.datetime):
  857. r = self.mk_tzaware(r)
  858. return r
  859. def __add__(self, other):
  860. return self.mk_tzaware(super().__add__(other))
  861. def astimezone(self, tzinfo):
  862. dt = super().astimezone(tzinfo)
  863. return self.mk_tzaware(dt)
  864. @classmethod
  865. def mk_tzaware(cls, datetime_obj):
  866. kwargs = {}
  867. attrs = ('year',
  868. 'month',
  869. 'day',
  870. 'hour',
  871. 'minute',
  872. 'second',
  873. 'microsecond',
  874. 'tzinfo')
  875. for attr in attrs:
  876. val = getattr(datetime_obj, attr, None)
  877. if val is not None:
  878. kwargs[attr] = val
  879. return cls(**kwargs)
  880. # Define a date_range function similar to pandas.date_range
  881. def date_range(start, freq, periods):
  882. dtstart = dt_tzaware.mk_tzaware(start)
  883. return [dtstart + (i * freq) for i in range(periods)]
  884. # Define a tz_convert function that converts a list to a new timezone.
  885. def tz_convert(dt_list, tzinfo):
  886. return [d.astimezone(tzinfo) for d in dt_list]
  887. _test_date2num_dst(date_range, tz_convert)
  888. def test_date2num_dst_pandas(pd):
  889. # Test for github issue #3896, but in date2num around DST transitions
  890. # with a timezone-aware pandas date_range object.
  891. def tz_convert(*args):
  892. return pd.DatetimeIndex.tz_convert(*args).astype(object)
  893. _test_date2num_dst(pd.date_range, tz_convert)
  894. def _test_rrulewrapper(attach_tz, get_tz):
  895. SYD = get_tz('Australia/Sydney')
  896. dtstart = attach_tz(datetime.datetime(2017, 4, 1, 0), SYD)
  897. dtend = attach_tz(datetime.datetime(2017, 4, 4, 0), SYD)
  898. rule = mdates.rrulewrapper(freq=dateutil.rrule.DAILY, dtstart=dtstart)
  899. act = rule.between(dtstart, dtend)
  900. exp = [datetime.datetime(2017, 4, 1, 13, tzinfo=dateutil.tz.tzutc()),
  901. datetime.datetime(2017, 4, 2, 14, tzinfo=dateutil.tz.tzutc())]
  902. assert act == exp
  903. def test_rrulewrapper():
  904. def attach_tz(dt, zi):
  905. return dt.replace(tzinfo=zi)
  906. _test_rrulewrapper(attach_tz, dateutil.tz.gettz)
  907. SYD = dateutil.tz.gettz('Australia/Sydney')
  908. dtstart = datetime.datetime(2017, 4, 1, 0)
  909. dtend = datetime.datetime(2017, 4, 4, 0)
  910. rule = mdates.rrulewrapper(freq=dateutil.rrule.DAILY, dtstart=dtstart,
  911. tzinfo=SYD, until=dtend)
  912. assert rule.after(dtstart) == datetime.datetime(2017, 4, 2, 0, 0,
  913. tzinfo=SYD)
  914. assert rule.before(dtend) == datetime.datetime(2017, 4, 3, 0, 0,
  915. tzinfo=SYD)
  916. # Test parts of __getattr__
  917. assert rule._base_tzinfo == SYD
  918. assert rule._interval == 1
  919. @pytest.mark.pytz
  920. def test_rrulewrapper_pytz():
  921. # Test to make sure pytz zones are supported in rrules
  922. pytz = pytest.importorskip("pytz")
  923. def attach_tz(dt, zi):
  924. return zi.localize(dt)
  925. _test_rrulewrapper(attach_tz, pytz.timezone)
  926. @pytest.mark.pytz
  927. def test_yearlocator_pytz():
  928. pytz = pytest.importorskip("pytz")
  929. tz = pytz.timezone('America/New_York')
  930. x = [tz.localize(datetime.datetime(2010, 1, 1))
  931. + datetime.timedelta(i) for i in range(2000)]
  932. locator = mdates.AutoDateLocator(interval_multiples=True, tz=tz)
  933. locator.create_dummy_axis()
  934. locator.axis.set_view_interval(mdates.date2num(x[0])-1.0,
  935. mdates.date2num(x[-1])+1.0)
  936. t = np.array([733408.208333, 733773.208333, 734138.208333,
  937. 734503.208333, 734869.208333, 735234.208333, 735599.208333])
  938. # convert to new epoch from old...
  939. t = t + mdates.date2num(np.datetime64('0000-12-31'))
  940. np.testing.assert_allclose(t, locator())
  941. expected = ['2009-01-01 00:00:00-05:00',
  942. '2010-01-01 00:00:00-05:00', '2011-01-01 00:00:00-05:00',
  943. '2012-01-01 00:00:00-05:00', '2013-01-01 00:00:00-05:00',
  944. '2014-01-01 00:00:00-05:00', '2015-01-01 00:00:00-05:00']
  945. st = list(map(str, mdates.num2date(locator(), tz=tz)))
  946. assert st == expected
  947. assert np.allclose(locator.tick_values(x[0], x[1]), np.array(
  948. [14610.20833333, 14610.33333333, 14610.45833333, 14610.58333333,
  949. 14610.70833333, 14610.83333333, 14610.95833333, 14611.08333333,
  950. 14611.20833333]))
  951. assert np.allclose(locator.get_locator(x[1], x[0]).tick_values(x[0], x[1]),
  952. np.array(
  953. [14610.20833333, 14610.33333333, 14610.45833333, 14610.58333333,
  954. 14610.70833333, 14610.83333333, 14610.95833333, 14611.08333333,
  955. 14611.20833333]))
  956. def test_YearLocator():
  957. def _create_year_locator(date1, date2, **kwargs):
  958. locator = mdates.YearLocator(**kwargs)
  959. locator.create_dummy_axis()
  960. locator.axis.set_view_interval(mdates.date2num(date1),
  961. mdates.date2num(date2))
  962. return locator
  963. d1 = datetime.datetime(1990, 1, 1)
  964. results = ([datetime.timedelta(weeks=52 * 200),
  965. {'base': 20, 'month': 1, 'day': 1},
  966. ['1980-01-01 00:00:00+00:00', '2000-01-01 00:00:00+00:00',
  967. '2020-01-01 00:00:00+00:00', '2040-01-01 00:00:00+00:00',
  968. '2060-01-01 00:00:00+00:00', '2080-01-01 00:00:00+00:00',
  969. '2100-01-01 00:00:00+00:00', '2120-01-01 00:00:00+00:00',
  970. '2140-01-01 00:00:00+00:00', '2160-01-01 00:00:00+00:00',
  971. '2180-01-01 00:00:00+00:00', '2200-01-01 00:00:00+00:00']
  972. ],
  973. [datetime.timedelta(weeks=52 * 200),
  974. {'base': 20, 'month': 5, 'day': 16},
  975. ['1980-05-16 00:00:00+00:00', '2000-05-16 00:00:00+00:00',
  976. '2020-05-16 00:00:00+00:00', '2040-05-16 00:00:00+00:00',
  977. '2060-05-16 00:00:00+00:00', '2080-05-16 00:00:00+00:00',
  978. '2100-05-16 00:00:00+00:00', '2120-05-16 00:00:00+00:00',
  979. '2140-05-16 00:00:00+00:00', '2160-05-16 00:00:00+00:00',
  980. '2180-05-16 00:00:00+00:00', '2200-05-16 00:00:00+00:00']
  981. ],
  982. [datetime.timedelta(weeks=52 * 5),
  983. {'base': 20, 'month': 9, 'day': 25},
  984. ['1980-09-25 00:00:00+00:00', '2000-09-25 00:00:00+00:00']
  985. ],
  986. )
  987. for delta, arguments, expected in results:
  988. d2 = d1 + delta
  989. locator = _create_year_locator(d1, d2, **arguments)
  990. assert list(map(str, mdates.num2date(locator()))) == expected
  991. def test_DayLocator():
  992. with pytest.raises(ValueError):
  993. mdates.DayLocator(interval=-1)
  994. with pytest.raises(ValueError):
  995. mdates.DayLocator(interval=-1.5)
  996. with pytest.raises(ValueError):
  997. mdates.DayLocator(interval=0)
  998. with pytest.raises(ValueError):
  999. mdates.DayLocator(interval=1.3)
  1000. mdates.DayLocator(interval=1.0)
  1001. def test_tz_utc():
  1002. dt = datetime.datetime(1970, 1, 1, tzinfo=mdates.UTC)
  1003. assert dt.tzname() == 'UTC'
  1004. @pytest.mark.parametrize("x, tdelta",
  1005. [(1, datetime.timedelta(days=1)),
  1006. ([1, 1.5], [datetime.timedelta(days=1),
  1007. datetime.timedelta(days=1.5)])])
  1008. def test_num2timedelta(x, tdelta):
  1009. dt = mdates.num2timedelta(x)
  1010. assert dt == tdelta
  1011. def test_datetime64_in_list():
  1012. dt = [np.datetime64('2000-01-01'), np.datetime64('2001-01-01')]
  1013. dn = mdates.date2num(dt)
  1014. # convert fixed values from old to new epoch
  1015. t = (np.array([730120., 730486.]) +
  1016. mdates.date2num(np.datetime64('0000-12-31')))
  1017. np.testing.assert_equal(dn, t)
  1018. def test_change_epoch():
  1019. date = np.datetime64('2000-01-01')
  1020. # use private method to clear the epoch and allow it to be set...
  1021. mdates._reset_epoch_test_example()
  1022. mdates.get_epoch() # Set default.
  1023. with pytest.raises(RuntimeError):
  1024. # this should fail here because there is a sentinel on the epoch
  1025. # if the epoch has been used then it cannot be set.
  1026. mdates.set_epoch('0000-01-01')
  1027. mdates._reset_epoch_test_example()
  1028. mdates.set_epoch('1970-01-01')
  1029. dt = (date - np.datetime64('1970-01-01')).astype('datetime64[D]')
  1030. dt = dt.astype('int')
  1031. np.testing.assert_equal(mdates.date2num(date), float(dt))
  1032. mdates._reset_epoch_test_example()
  1033. mdates.set_epoch('0000-12-31')
  1034. np.testing.assert_equal(mdates.date2num(date), 730120.0)
  1035. mdates._reset_epoch_test_example()
  1036. mdates.set_epoch('1970-01-01T01:00:00')
  1037. np.testing.assert_allclose(mdates.date2num(date), dt - 1./24.)
  1038. mdates._reset_epoch_test_example()
  1039. mdates.set_epoch('1970-01-01T00:00:00')
  1040. np.testing.assert_allclose(
  1041. mdates.date2num(np.datetime64('1970-01-01T12:00:00')),
  1042. 0.5)
  1043. def test_warn_notintervals():
  1044. dates = np.arange('2001-01-10', '2001-03-04', dtype='datetime64[D]')
  1045. locator = mdates.AutoDateLocator(interval_multiples=False)
  1046. locator.intervald[3] = [2]
  1047. locator.create_dummy_axis()
  1048. locator.axis.set_view_interval(mdates.date2num(dates[0]),
  1049. mdates.date2num(dates[-1]))
  1050. with pytest.warns(UserWarning, match="AutoDateLocator was unable"):
  1051. locs = locator()
  1052. def test_change_converter():
  1053. plt.rcParams['date.converter'] = 'concise'
  1054. dates = np.arange('2020-01-01', '2020-05-01', dtype='datetime64[D]')
  1055. fig, ax = plt.subplots()
  1056. ax.plot(dates, np.arange(len(dates)))
  1057. fig.canvas.draw()
  1058. assert ax.get_xticklabels()[0].get_text() == 'Jan'
  1059. assert ax.get_xticklabels()[1].get_text() == '15'
  1060. plt.rcParams['date.converter'] = 'auto'
  1061. fig, ax = plt.subplots()
  1062. ax.plot(dates, np.arange(len(dates)))
  1063. fig.canvas.draw()
  1064. assert ax.get_xticklabels()[0].get_text() == 'Jan 01 2020'
  1065. assert ax.get_xticklabels()[1].get_text() == 'Jan 15 2020'
  1066. with pytest.raises(ValueError):
  1067. plt.rcParams['date.converter'] = 'boo'
  1068. def test_change_interval_multiples():
  1069. plt.rcParams['date.interval_multiples'] = False
  1070. dates = np.arange('2020-01-10', '2020-05-01', dtype='datetime64[D]')
  1071. fig, ax = plt.subplots()
  1072. ax.plot(dates, np.arange(len(dates)))
  1073. fig.canvas.draw()
  1074. assert ax.get_xticklabels()[0].get_text() == 'Jan 10 2020'
  1075. assert ax.get_xticklabels()[1].get_text() == 'Jan 24 2020'
  1076. plt.rcParams['date.interval_multiples'] = 'True'
  1077. fig, ax = plt.subplots()
  1078. ax.plot(dates, np.arange(len(dates)))
  1079. fig.canvas.draw()
  1080. assert ax.get_xticklabels()[0].get_text() == 'Jan 15 2020'
  1081. assert ax.get_xticklabels()[1].get_text() == 'Feb 01 2020'
  1082. def test_julian2num():
  1083. mdates._reset_epoch_test_example()
  1084. mdates.set_epoch('0000-12-31')
  1085. with pytest.warns(mpl.MatplotlibDeprecationWarning):
  1086. # 2440587.5 is julian date for 1970-01-01T00:00:00
  1087. # https://en.wikipedia.org/wiki/Julian_day
  1088. assert mdates.julian2num(2440588.5) == 719164.0
  1089. assert mdates.num2julian(719165.0) == 2440589.5
  1090. # set back to the default
  1091. mdates._reset_epoch_test_example()
  1092. mdates.set_epoch('1970-01-01T00:00:00')
  1093. with pytest.warns(mpl.MatplotlibDeprecationWarning):
  1094. assert mdates.julian2num(2440588.5) == 1.0
  1095. assert mdates.num2julian(2.0) == 2440589.5
  1096. def test_DateLocator():
  1097. locator = mdates.DateLocator()
  1098. # Test nonsingular
  1099. assert locator.nonsingular(0, np.inf) == (0, 1)
  1100. assert locator.nonsingular(0, 1) == (0, 1)
  1101. assert locator.nonsingular(1, 0) == (0, 1)
  1102. assert locator.nonsingular(0, 0) == (-2, 2)
  1103. locator.create_dummy_axis()
  1104. # default values
  1105. assert locator.datalim_to_dt() == (
  1106. datetime.datetime(1970, 1, 1, 0, 0, tzinfo=datetime.timezone.utc),
  1107. datetime.datetime(1970, 1, 2, 0, 0, tzinfo=datetime.timezone.utc))
  1108. # Check default is UTC
  1109. assert locator.tz == mdates.UTC
  1110. tz_str = 'Iceland'
  1111. iceland_tz = dateutil.tz.gettz(tz_str)
  1112. # Check not Iceland
  1113. assert locator.tz != iceland_tz
  1114. # Set it to Iceland
  1115. locator.set_tzinfo('Iceland')
  1116. # Check now it is Iceland
  1117. assert locator.tz == iceland_tz
  1118. locator.create_dummy_axis()
  1119. locator.axis.set_data_interval(*mdates.date2num(["2022-01-10",
  1120. "2022-01-08"]))
  1121. assert locator.datalim_to_dt() == (
  1122. datetime.datetime(2022, 1, 8, 0, 0, tzinfo=iceland_tz),
  1123. datetime.datetime(2022, 1, 10, 0, 0, tzinfo=iceland_tz))
  1124. # Set rcParam
  1125. plt.rcParams['timezone'] = tz_str
  1126. # Create a new one in a similar way
  1127. locator = mdates.DateLocator()
  1128. # Check now it is Iceland
  1129. assert locator.tz == iceland_tz
  1130. # Test invalid tz values
  1131. with pytest.raises(ValueError, match="Aiceland is not a valid timezone"):
  1132. mdates.DateLocator(tz="Aiceland")
  1133. with pytest.raises(TypeError,
  1134. match="tz must be string or tzinfo subclass."):
  1135. mdates.DateLocator(tz=1)
  1136. def test_datestr2num():
  1137. assert mdates.datestr2num('2022-01-10') == 19002.0
  1138. dt = datetime.date(year=2022, month=1, day=10)
  1139. assert mdates.datestr2num('2022-01', default=dt) == 19002.0
  1140. assert np.all(mdates.datestr2num(
  1141. ['2022-01', '2022-02'], default=dt
  1142. ) == np.array([19002., 19033.]))
  1143. assert mdates.datestr2num([]).size == 0
  1144. assert mdates.datestr2num([], datetime.date(year=2022,
  1145. month=1, day=10)).size == 0
  1146. @pytest.mark.parametrize('kwarg',
  1147. ('formats', 'zero_formats', 'offset_formats'))
  1148. def test_concise_formatter_exceptions(kwarg):
  1149. locator = mdates.AutoDateLocator()
  1150. kwargs = {kwarg: ['', '%Y']}
  1151. match = f"{kwarg} argument must be a list"
  1152. with pytest.raises(ValueError, match=match):
  1153. mdates.ConciseDateFormatter(locator, **kwargs)
  1154. def test_concise_formatter_call():
  1155. locator = mdates.AutoDateLocator()
  1156. formatter = mdates.ConciseDateFormatter(locator)
  1157. assert formatter(19002.0) == '2022'
  1158. assert formatter.format_data_short(19002.0) == '2022-01-10 00:00:00'
  1159. def test_datetime_masked():
  1160. # make sure that all-masked data falls back to the viewlim
  1161. # set in convert.axisinfo....
  1162. x = np.array([datetime.datetime(2017, 1, n) for n in range(1, 6)])
  1163. y = np.array([1, 2, 3, 4, 5])
  1164. m = np.ma.masked_greater(y, 0)
  1165. fig, ax = plt.subplots()
  1166. ax.plot(x, m)
  1167. assert ax.get_xlim() == (0, 1)
  1168. @pytest.mark.parametrize('val', (-1000000, 10000000))
  1169. def test_num2date_error(val):
  1170. with pytest.raises(ValueError, match=f"Date ordinal {val} converts"):
  1171. mdates.num2date(val)
  1172. def test_num2date_roundoff():
  1173. assert mdates.num2date(100000.0000578702) == datetime.datetime(
  1174. 2243, 10, 17, 0, 0, 4, 999980, tzinfo=datetime.timezone.utc)
  1175. # Slightly larger, steps of 20 microseconds
  1176. assert mdates.num2date(100000.0000578703) == datetime.datetime(
  1177. 2243, 10, 17, 0, 0, 5, tzinfo=datetime.timezone.utc)
  1178. def test_DateFormatter_settz():
  1179. time = mdates.date2num(datetime.datetime(2011, 1, 1, 0, 0,
  1180. tzinfo=mdates.UTC))
  1181. formatter = mdates.DateFormatter('%Y-%b-%d %H:%M')
  1182. # Default UTC
  1183. assert formatter(time) == '2011-Jan-01 00:00'
  1184. # Set tzinfo
  1185. formatter.set_tzinfo('Pacific/Kiritimati')
  1186. assert formatter(time) == '2011-Jan-01 14:00'