test_dates.py 55 KB

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