dates.py 65 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970
  1. """
  2. Matplotlib provides sophisticated date plotting capabilities, standing on the
  3. shoulders of python :mod:`datetime` and the add-on module :mod:`dateutil`.
  4. .. _date-format:
  5. Matplotlib date format
  6. ----------------------
  7. Matplotlib represents dates using floating point numbers specifying the number
  8. of days since 0001-01-01 UTC, plus 1. For example, 0001-01-01, 06:00 is 1.25,
  9. not 0.25. Values < 1, i.e. dates before 0001-01-01 UTC, are not supported.
  10. There are a number of helper functions to convert between :mod:`datetime`
  11. objects and Matplotlib dates:
  12. .. currentmodule:: matplotlib.dates
  13. .. autosummary::
  14. :nosignatures:
  15. datestr2num
  16. date2num
  17. num2date
  18. num2timedelta
  19. epoch2num
  20. num2epoch
  21. drange
  22. .. note::
  23. Like Python's datetime, Matplotlib uses the Gregorian calendar for all
  24. conversions between dates and floating point numbers. This practice
  25. is not universal, and calendar differences can cause confusing
  26. differences between what Python and Matplotlib give as the number of days
  27. since 0001-01-01 and what other software and databases yield. For
  28. example, the US Naval Observatory uses a calendar that switches
  29. from Julian to Gregorian in October, 1582. Hence, using their
  30. calculator, the number of days between 0001-01-01 and 2006-04-01 is
  31. 732403, whereas using the Gregorian calendar via the datetime
  32. module we find::
  33. In [1]: date(2006, 4, 1).toordinal() - date(1, 1, 1).toordinal()
  34. Out[1]: 732401
  35. All the Matplotlib date converters, tickers and formatters are timezone aware.
  36. If no explicit timezone is provided, :rc:`timezone` is assumed. If you want to
  37. use a custom time zone, pass a `datetime.tzinfo` instance with the tz keyword
  38. argument to `num2date`, `~.Axes.plot_date`, and any custom date tickers or
  39. locators you create.
  40. A wide range of specific and general purpose date tick locators and
  41. formatters are provided in this module. See
  42. :mod:`matplotlib.ticker` for general information on tick locators
  43. and formatters. These are described below.
  44. The dateutil_ module provides additional code to handle date ticking, making it
  45. easy to place ticks on any kinds of dates. See examples below.
  46. .. _dateutil: https://dateutil.readthedocs.io
  47. Date tickers
  48. ------------
  49. Most of the date tickers can locate single or multiple values. For
  50. example::
  51. # import constants for the days of the week
  52. from matplotlib.dates import MO, TU, WE, TH, FR, SA, SU
  53. # tick on mondays every week
  54. loc = WeekdayLocator(byweekday=MO, tz=tz)
  55. # tick on mondays and saturdays
  56. loc = WeekdayLocator(byweekday=(MO, SA))
  57. In addition, most of the constructors take an interval argument::
  58. # tick on mondays every second week
  59. loc = WeekdayLocator(byweekday=MO, interval=2)
  60. The rrule locator allows completely general date ticking::
  61. # tick every 5th easter
  62. rule = rrulewrapper(YEARLY, byeaster=1, interval=5)
  63. loc = RRuleLocator(rule)
  64. The available date tickers are:
  65. * `MicrosecondLocator`: locate microseconds
  66. * `SecondLocator`: locate seconds
  67. * `MinuteLocator`: locate minutes
  68. * `HourLocator`: locate hours
  69. * `DayLocator`: locate specified days of the month
  70. * `WeekdayLocator`: Locate days of the week, e.g., MO, TU
  71. * `MonthLocator`: locate months, e.g., 7 for july
  72. * `YearLocator`: locate years that are multiples of base
  73. * `RRuleLocator`: locate using a `matplotlib.dates.rrulewrapper`.
  74. `.rrulewrapper` is a simple wrapper around dateutil_'s `dateutil.rrule` which
  75. allow almost arbitrary date tick specifications. See :doc:`rrule example
  76. </gallery/ticks_and_spines/date_demo_rrule>`.
  77. * `AutoDateLocator`: On autoscale, this class picks the best `DateLocator`
  78. (e.g., `RRuleLocator`) to set the view limits and the tick locations. If
  79. called with ``interval_multiples=True`` it will make ticks line up with
  80. sensible multiples of the tick intervals. E.g. if the interval is 4 hours,
  81. it will pick hours 0, 4, 8, etc as ticks. This behaviour is not guaranteed
  82. by default.
  83. Date formatters
  84. ---------------
  85. The available date formatters are:
  86. * `AutoDateFormatter`: attempts to figure out the best format to use. This is
  87. most useful when used with the `AutoDateLocator`.
  88. * `ConciseDateFormatter`: also attempts to figure out the best format to use,
  89. and to make the format as compact as possible while still having complete
  90. date information. This is most useful when used with the `AutoDateLocator`.
  91. * `DateFormatter`: use `~datetime.datetime.strftime` format strings.
  92. * `IndexDateFormatter`: date plots with implicit *x* indexing.
  93. """
  94. import datetime
  95. import functools
  96. import logging
  97. import math
  98. import re
  99. import time
  100. import warnings
  101. from dateutil.rrule import (rrule, MO, TU, WE, TH, FR, SA, SU, YEARLY,
  102. MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY,
  103. SECONDLY)
  104. from dateutil.relativedelta import relativedelta
  105. import dateutil.parser
  106. import dateutil.tz
  107. import numpy as np
  108. import matplotlib
  109. from matplotlib import rcParams
  110. import matplotlib.units as units
  111. import matplotlib.cbook as cbook
  112. import matplotlib.ticker as ticker
  113. __all__ = ('datestr2num', 'date2num', 'num2date', 'num2timedelta', 'drange',
  114. 'epoch2num', 'num2epoch', 'mx2num', 'DateFormatter',
  115. 'ConciseDateFormatter', 'IndexDateFormatter', 'AutoDateFormatter',
  116. 'DateLocator', 'RRuleLocator', 'AutoDateLocator', 'YearLocator',
  117. 'MonthLocator', 'WeekdayLocator',
  118. 'DayLocator', 'HourLocator', 'MinuteLocator',
  119. 'SecondLocator', 'MicrosecondLocator',
  120. 'rrule', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU',
  121. 'YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY',
  122. 'HOURLY', 'MINUTELY', 'SECONDLY', 'MICROSECONDLY', 'relativedelta',
  123. 'seconds', 'minutes', 'hours', 'weeks')
  124. _log = logging.getLogger(__name__)
  125. UTC = datetime.timezone.utc
  126. def _get_rc_timezone():
  127. """Retrieve the preferred timezone from the rcParams dictionary."""
  128. s = matplotlib.rcParams['timezone']
  129. if s == 'UTC':
  130. return UTC
  131. return dateutil.tz.gettz(s)
  132. """
  133. Time-related constants.
  134. """
  135. EPOCH_OFFSET = float(datetime.datetime(1970, 1, 1).toordinal())
  136. JULIAN_OFFSET = 1721424.5 # Julian date at 0001-01-01
  137. MICROSECONDLY = SECONDLY + 1
  138. HOURS_PER_DAY = 24.
  139. MIN_PER_HOUR = 60.
  140. SEC_PER_MIN = 60.
  141. MONTHS_PER_YEAR = 12.
  142. DAYS_PER_WEEK = 7.
  143. DAYS_PER_MONTH = 30.
  144. DAYS_PER_YEAR = 365.0
  145. MINUTES_PER_DAY = MIN_PER_HOUR * HOURS_PER_DAY
  146. SEC_PER_HOUR = SEC_PER_MIN * MIN_PER_HOUR
  147. SEC_PER_DAY = SEC_PER_HOUR * HOURS_PER_DAY
  148. SEC_PER_WEEK = SEC_PER_DAY * DAYS_PER_WEEK
  149. MUSECONDS_PER_DAY = 1e6 * SEC_PER_DAY
  150. MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY = (
  151. MO, TU, WE, TH, FR, SA, SU)
  152. WEEKDAYS = (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY)
  153. def _to_ordinalf(dt):
  154. """
  155. Convert :mod:`datetime` or :mod:`date` to the Gregorian date as UTC float
  156. days, preserving hours, minutes, seconds and microseconds. Return value
  157. is a :func:`float`.
  158. """
  159. # Convert to UTC
  160. tzi = getattr(dt, 'tzinfo', None)
  161. if tzi is not None:
  162. dt = dt.astimezone(UTC)
  163. tzi = UTC
  164. base = float(dt.toordinal())
  165. # If it's sufficiently datetime-like, it will have a `date()` method
  166. cdate = getattr(dt, 'date', lambda: None)()
  167. if cdate is not None:
  168. # Get a datetime object at midnight UTC
  169. midnight_time = datetime.time(0, tzinfo=tzi)
  170. rdt = datetime.datetime.combine(cdate, midnight_time)
  171. # Append the seconds as a fraction of a day
  172. base += (dt - rdt).total_seconds() / SEC_PER_DAY
  173. return base
  174. # a version of _to_ordinalf that can operate on numpy arrays
  175. _to_ordinalf_np_vectorized = np.vectorize(_to_ordinalf)
  176. def _dt64_to_ordinalf(d):
  177. """
  178. Convert `numpy.datetime64` or an ndarray of those types to Gregorian
  179. date as UTC float. Roundoff is via float64 precision. Practically:
  180. microseconds for dates between 290301 BC, 294241 AD, milliseconds for
  181. larger dates (see `numpy.datetime64`). Nanoseconds aren't possible
  182. because we do times compared to ``0001-01-01T00:00:00`` (plus one day).
  183. """
  184. # the "extra" ensures that we at least allow the dynamic range out to
  185. # seconds. That should get out to +/-2e11 years.
  186. extra = (d - d.astype('datetime64[s]')).astype('timedelta64[ns]')
  187. t0 = np.datetime64('0001-01-01T00:00:00', 's')
  188. dt = (d.astype('datetime64[s]') - t0).astype(np.float64)
  189. dt += extra.astype(np.float64) / 1.0e9
  190. dt = dt / SEC_PER_DAY + 1.0
  191. NaT_int = np.datetime64('NaT').astype(np.int64)
  192. d_int = d.astype(np.int64)
  193. try:
  194. dt[d_int == NaT_int] = np.nan
  195. except TypeError:
  196. if d_int == NaT_int:
  197. dt = np.nan
  198. return dt
  199. def _from_ordinalf(x, tz=None):
  200. """
  201. Convert Gregorian float of the date, preserving hours, minutes,
  202. seconds and microseconds. Return value is a `.datetime`.
  203. The input date *x* is a float in ordinal days at UTC, and the output will
  204. be the specified `.datetime` object corresponding to that time in
  205. timezone *tz*, or if *tz* is ``None``, in the timezone specified in
  206. :rc:`timezone`.
  207. """
  208. if tz is None:
  209. tz = _get_rc_timezone()
  210. ix, remainder = divmod(x, 1)
  211. ix = int(ix)
  212. if ix < 1:
  213. raise ValueError('Cannot convert {} to a date. This often happens if '
  214. 'non-datetime values are passed to an axis that '
  215. 'expects datetime objects.'.format(ix))
  216. dt = datetime.datetime.fromordinal(ix).replace(tzinfo=UTC)
  217. # Since the input date *x* float is unable to preserve microsecond
  218. # precision of time representation in non-antique years, the
  219. # resulting datetime is rounded to the nearest multiple of
  220. # `musec_prec`. A value of 20 is appropriate for current dates.
  221. musec_prec = 20
  222. remainder_musec = int(round(remainder * MUSECONDS_PER_DAY / musec_prec)
  223. * musec_prec)
  224. # For people trying to plot with full microsecond precision, enable
  225. # an early-year workaround
  226. if x < 30 * 365:
  227. remainder_musec = int(round(remainder * MUSECONDS_PER_DAY))
  228. # add hours, minutes, seconds, microseconds
  229. dt += datetime.timedelta(microseconds=remainder_musec)
  230. return dt.astimezone(tz)
  231. # a version of _from_ordinalf that can operate on numpy arrays
  232. _from_ordinalf_np_vectorized = np.vectorize(_from_ordinalf)
  233. @cbook.deprecated(
  234. "3.1", alternative="time.strptime or dateutil.parser.parse or datestr2num")
  235. class strpdate2num:
  236. """
  237. Use this class to parse date strings to matplotlib datenums when
  238. you know the date format string of the date you are parsing.
  239. """
  240. def __init__(self, fmt):
  241. """
  242. Parameters
  243. ----------
  244. fmt : any valid strptime format
  245. """
  246. self.fmt = fmt
  247. def __call__(self, s):
  248. """
  249. Parameters
  250. ----------
  251. s : str
  252. Returns
  253. -------
  254. date2num float
  255. """
  256. return date2num(datetime.datetime(*time.strptime(s, self.fmt)[:6]))
  257. @cbook.deprecated(
  258. "3.1", alternative="time.strptime or dateutil.parser.parse or datestr2num")
  259. class bytespdate2num(strpdate2num):
  260. """
  261. Use this class to parse date strings to matplotlib datenums when
  262. you know the date format string of the date you are parsing. See
  263. :doc:`/gallery/misc/load_converter.py`.
  264. """
  265. def __init__(self, fmt, encoding='utf-8'):
  266. """
  267. Parameters
  268. ----------
  269. fmt : any valid strptime format
  270. encoding : str
  271. Encoding to use on byte input.
  272. """
  273. super().__init__(fmt)
  274. self.encoding = encoding
  275. def __call__(self, b):
  276. """
  277. Parameters
  278. ----------
  279. b : bytes
  280. Returns
  281. -------
  282. date2num float
  283. """
  284. s = b.decode(self.encoding)
  285. return super().__call__(s)
  286. # a version of dateutil.parser.parse that can operate on numpy arrays
  287. _dateutil_parser_parse_np_vectorized = np.vectorize(dateutil.parser.parse)
  288. def datestr2num(d, default=None):
  289. """
  290. Convert a date string to a datenum using :func:`dateutil.parser.parse`.
  291. Parameters
  292. ----------
  293. d : str or sequence of str
  294. The dates to convert.
  295. default : datetime instance, optional
  296. The default date to use when fields are missing in *d*.
  297. """
  298. if isinstance(d, str):
  299. dt = dateutil.parser.parse(d, default=default)
  300. return date2num(dt)
  301. else:
  302. if default is not None:
  303. d = [dateutil.parser.parse(s, default=default) for s in d]
  304. d = np.asarray(d)
  305. if not d.size:
  306. return d
  307. return date2num(_dateutil_parser_parse_np_vectorized(d))
  308. def date2num(d):
  309. """
  310. Convert datetime objects to Matplotlib dates.
  311. Parameters
  312. ----------
  313. d : `datetime.datetime` or `numpy.datetime64` or sequences of these
  314. Returns
  315. -------
  316. float or sequence of floats
  317. Number of days (fraction part represents hours, minutes, seconds, ms)
  318. since 0001-01-01 00:00:00 UTC, plus one.
  319. Notes
  320. -----
  321. The addition of one here is a historical artifact. Also, note that the
  322. Gregorian calendar is assumed; this is not universal practice.
  323. For details see the module docstring.
  324. """
  325. if hasattr(d, "values"):
  326. # this unpacks pandas series or dataframes...
  327. d = d.values
  328. if not np.iterable(d):
  329. if (isinstance(d, np.datetime64) or
  330. (isinstance(d, np.ndarray) and
  331. np.issubdtype(d.dtype, np.datetime64))):
  332. return _dt64_to_ordinalf(d)
  333. return _to_ordinalf(d)
  334. else:
  335. d = np.asarray(d)
  336. if np.issubdtype(d.dtype, np.datetime64):
  337. return _dt64_to_ordinalf(d)
  338. if not d.size:
  339. return d
  340. return _to_ordinalf_np_vectorized(d)
  341. def julian2num(j):
  342. """
  343. Convert a Julian date (or sequence) to a Matplotlib date (or sequence).
  344. Parameters
  345. ----------
  346. j : float or sequence of floats
  347. Julian date(s)
  348. Returns
  349. -------
  350. float or sequence of floats
  351. Matplotlib date(s)
  352. """
  353. return np.subtract(j, JULIAN_OFFSET) # Handles both scalar & nonscalar j.
  354. def num2julian(n):
  355. """
  356. Convert a Matplotlib date (or sequence) to a Julian date (or sequence).
  357. Parameters
  358. ----------
  359. n : float or sequence of floats
  360. Matplotlib date(s)
  361. Returns
  362. -------
  363. float or sequence of floats
  364. Julian date(s)
  365. """
  366. return np.add(n, JULIAN_OFFSET) # Handles both scalar & nonscalar j.
  367. def num2date(x, tz=None):
  368. """
  369. Convert Matplotlib dates to `~datetime.datetime` objects.
  370. Parameters
  371. ----------
  372. x : float or sequence of floats
  373. Number of days (fraction part represents hours, minutes, seconds)
  374. since 0001-01-01 00:00:00 UTC, plus one.
  375. tz : str, optional
  376. Timezone of *x* (defaults to rcparams ``timezone``).
  377. Returns
  378. -------
  379. `~datetime.datetime` or sequence of `~datetime.datetime`
  380. Dates are returned in timezone *tz*.
  381. If *x* is a sequence, a sequence of :class:`datetime` objects will
  382. be returned.
  383. Notes
  384. -----
  385. The addition of one here is a historical artifact. Also, note that the
  386. Gregorian calendar is assumed; this is not universal practice.
  387. For details, see the module docstring.
  388. """
  389. if tz is None:
  390. tz = _get_rc_timezone()
  391. if not np.iterable(x):
  392. return _from_ordinalf(x, tz)
  393. else:
  394. x = np.asarray(x)
  395. if not x.size:
  396. return x
  397. return _from_ordinalf_np_vectorized(x, tz).tolist()
  398. def _ordinalf_to_timedelta(x):
  399. return datetime.timedelta(days=x)
  400. _ordinalf_to_timedelta_np_vectorized = np.vectorize(_ordinalf_to_timedelta)
  401. def num2timedelta(x):
  402. """
  403. Convert number of days to a `~datetime.timedelta` object.
  404. If *x* is a sequence, a sequence of `~datetime.timedelta` objects will
  405. be returned.
  406. Parameters
  407. ----------
  408. x : float, sequence of floats
  409. Number of days. The fraction part represents hours, minutes, seconds.
  410. Returns
  411. -------
  412. `datetime.timedelta` or list[`datetime.timedelta`]
  413. """
  414. if not np.iterable(x):
  415. return _ordinalf_to_timedelta(x)
  416. else:
  417. x = np.asarray(x)
  418. if not x.size:
  419. return x
  420. return _ordinalf_to_timedelta_np_vectorized(x).tolist()
  421. def drange(dstart, dend, delta):
  422. """
  423. Return a sequence of equally spaced Matplotlib dates.
  424. The dates start at *dstart* and reach up to, but not including *dend*.
  425. They are spaced by *delta*.
  426. Parameters
  427. ----------
  428. dstart, dend : `~datetime.datetime`
  429. The date limits.
  430. delta : `datetime.timedelta`
  431. Spacing of the dates.
  432. Returns
  433. -------
  434. drange : `numpy.array`
  435. A list floats representing Matplotlib dates.
  436. """
  437. f1 = date2num(dstart)
  438. f2 = date2num(dend)
  439. step = delta.total_seconds() / SEC_PER_DAY
  440. # calculate the difference between dend and dstart in times of delta
  441. num = int(np.ceil((f2 - f1) / step))
  442. # calculate end of the interval which will be generated
  443. dinterval_end = dstart + num * delta
  444. # ensure, that an half open interval will be generated [dstart, dend)
  445. if dinterval_end >= dend:
  446. # if the endpoint is greater than dend, just subtract one delta
  447. dinterval_end -= delta
  448. num -= 1
  449. f2 = date2num(dinterval_end) # new float-endpoint
  450. return np.linspace(f1, f2, num + 1)
  451. ## date tickers and formatters ###
  452. class DateFormatter(ticker.Formatter):
  453. """
  454. Format a tick (in days since the epoch) with a
  455. `~datetime.datetime.strftime` format string.
  456. """
  457. illegal_s = re.compile(r"((^|[^%])(%%)*%s)")
  458. def __init__(self, fmt, tz=None):
  459. """
  460. Parameters
  461. ----------
  462. fmt : str
  463. `~datetime.datetime.strftime` format string
  464. tz : `tzinfo`, default: :rc:`timezone`
  465. Ticks timezone.
  466. """
  467. if tz is None:
  468. tz = _get_rc_timezone()
  469. self.fmt = fmt
  470. self.tz = tz
  471. def __call__(self, x, pos=0):
  472. if x == 0:
  473. raise ValueError('DateFormatter found a value of x=0, which is '
  474. 'an illegal date; this usually occurs because '
  475. 'you have not informed the axis that it is '
  476. 'plotting dates, e.g., with ax.xaxis_date()')
  477. return num2date(x, self.tz).strftime(self.fmt)
  478. def set_tzinfo(self, tz):
  479. self.tz = tz
  480. class IndexDateFormatter(ticker.Formatter):
  481. """Use with `.IndexLocator` to cycle format strings by index."""
  482. def __init__(self, t, fmt, tz=None):
  483. """
  484. *t* is a sequence of dates (floating point days). *fmt* is a
  485. `~datetime.datetime.strftime` format string.
  486. """
  487. if tz is None:
  488. tz = _get_rc_timezone()
  489. self.t = t
  490. self.fmt = fmt
  491. self.tz = tz
  492. def __call__(self, x, pos=0):
  493. 'Return the label for time *x* at position *pos*'
  494. ind = int(round(x))
  495. if ind >= len(self.t) or ind <= 0:
  496. return ''
  497. return num2date(self.t[ind], self.tz).strftime(self.fmt)
  498. class ConciseDateFormatter(ticker.Formatter):
  499. """
  500. This class attempts to figure out the best format to use for the
  501. date, and to make it as compact as possible, but still be complete. This is
  502. most useful when used with the `AutoDateLocator`::
  503. >>> locator = AutoDateLocator()
  504. >>> formatter = ConciseDateFormatter(locator)
  505. Parameters
  506. ----------
  507. locator : `.ticker.Locator`
  508. Locator that this axis is using.
  509. tz : str, optional
  510. Passed to `.dates.date2num`.
  511. formats : list of 6 strings, optional
  512. Format strings for 6 levels of tick labelling: mostly years,
  513. months, days, hours, minutes, and seconds. Strings use
  514. the same format codes as `strftime`. Default is
  515. ``['%Y', '%b', '%d', '%H:%M', '%H:%M', '%S.%f']``
  516. zero_formats : list of 6 strings, optional
  517. Format strings for tick labels that are "zeros" for a given tick
  518. level. For instance, if most ticks are months, ticks around 1 Jan 2005
  519. will be labeled "Dec", "2005", "Feb". The default is
  520. ``['', '%Y', '%b', '%b-%d', '%H:%M', '%H:%M']``
  521. offset_formats : list of 6 strings, optional
  522. Format strings for the 6 levels that is applied to the "offset"
  523. string found on the right side of an x-axis, or top of a y-axis.
  524. Combined with the tick labels this should completely specify the
  525. date. The default is::
  526. ['', '%Y', '%Y-%b', '%Y-%b-%d', '%Y-%b-%d', '%Y-%b-%d %H:%M']
  527. show_offset : bool
  528. Whether to show the offset or not. Default is ``True``.
  529. Examples
  530. --------
  531. See :doc:`/gallery/ticks_and_spines/date_concise_formatter`
  532. .. plot::
  533. import datetime
  534. import matplotlib.dates as mdates
  535. base = datetime.datetime(2005, 2, 1)
  536. dates = np.array([base + datetime.timedelta(hours=(2 * i))
  537. for i in range(732)])
  538. N = len(dates)
  539. np.random.seed(19680801)
  540. y = np.cumsum(np.random.randn(N))
  541. fig, ax = plt.subplots(constrained_layout=True)
  542. locator = mdates.AutoDateLocator()
  543. formatter = mdates.ConciseDateFormatter(locator)
  544. ax.xaxis.set_major_locator(locator)
  545. ax.xaxis.set_major_formatter(formatter)
  546. ax.plot(dates, y)
  547. ax.set_title('Concise Date Formatter')
  548. """
  549. def __init__(self, locator, tz=None, formats=None, offset_formats=None,
  550. zero_formats=None, show_offset=True):
  551. """
  552. Autoformat the date labels. The default format is used to form an
  553. initial string, and then redundant elements are removed.
  554. """
  555. self._locator = locator
  556. self._tz = tz
  557. self.defaultfmt = '%Y'
  558. # there are 6 levels with each level getting a specific format
  559. # 0: mostly years, 1: months, 2: days,
  560. # 3: hours, 4: minutes, 5: seconds
  561. if formats:
  562. if len(formats) != 6:
  563. raise ValueError('formats argument must be a list of '
  564. '6 format strings (or None)')
  565. self.formats = formats
  566. else:
  567. self.formats = ['%Y', # ticks are mostly years
  568. '%b', # ticks are mostly months
  569. '%d', # ticks are mostly days
  570. '%H:%M', # hrs
  571. '%H:%M', # min
  572. '%S.%f', # secs
  573. ]
  574. # fmt for zeros ticks at this level. These are
  575. # ticks that should be labeled w/ info the level above.
  576. # like 1 Jan can just be labled "Jan". 02:02:00 can
  577. # just be labeled 02:02.
  578. if zero_formats:
  579. if len(zero_formats) != 6:
  580. raise ValueError('zero_formats argument must be a list of '
  581. '6 format strings (or None)')
  582. self.zero_formats = zero_formats
  583. elif formats:
  584. # use the users formats for the zero tick formats
  585. self.zero_formats = [''] + self.formats[:-1]
  586. else:
  587. # make the defaults a bit nicer:
  588. self.zero_formats = [''] + self.formats[:-1]
  589. self.zero_formats[3] = '%b-%d'
  590. if offset_formats:
  591. if len(offset_formats) != 6:
  592. raise ValueError('offsetfmts argument must be a list of '
  593. '6 format strings (or None)')
  594. self.offset_formats = offset_formats
  595. else:
  596. self.offset_formats = ['',
  597. '%Y',
  598. '%Y-%b',
  599. '%Y-%b-%d',
  600. '%Y-%b-%d',
  601. '%Y-%b-%d %H:%M']
  602. self.offset_string = ''
  603. self.show_offset = show_offset
  604. def __call__(self, x, pos=None):
  605. formatter = DateFormatter(self.defaultfmt, self._tz)
  606. return formatter(x, pos=pos)
  607. def format_ticks(self, values):
  608. tickdatetime = [num2date(value, tz=self._tz) for value in values]
  609. tickdate = np.array([tdt.timetuple()[:6] for tdt in tickdatetime])
  610. # basic algorithm:
  611. # 1) only display a part of the date if it changes over the ticks.
  612. # 2) don't display the smaller part of the date if:
  613. # it is always the same or if it is the start of the
  614. # year, month, day etc.
  615. # fmt for most ticks at this level
  616. fmts = self.formats
  617. # format beginnings of days, months, years, etc...
  618. zerofmts = self.zero_formats
  619. # offset fmt are for the offset in the upper left of the
  620. # or lower right of the axis.
  621. offsetfmts = self.offset_formats
  622. # determine the level we will label at:
  623. # mostly 0: years, 1: months, 2: days,
  624. # 3: hours, 4: minutes, 5: seconds, 6: microseconds
  625. for level in range(5, -1, -1):
  626. if len(np.unique(tickdate[:, level])) > 1:
  627. break
  628. # level is the basic level we will label at.
  629. # now loop through and decide the actual ticklabels
  630. zerovals = [0, 1, 1, 0, 0, 0, 0]
  631. labels = [''] * len(tickdate)
  632. for nn in range(len(tickdate)):
  633. if level < 5:
  634. if tickdate[nn][level] == zerovals[level]:
  635. fmt = zerofmts[level]
  636. else:
  637. fmt = fmts[level]
  638. else:
  639. # special handling for seconds + microseconds
  640. if (tickdatetime[nn].second == tickdatetime[nn].microsecond
  641. == 0):
  642. fmt = zerofmts[level]
  643. else:
  644. fmt = fmts[level]
  645. labels[nn] = tickdatetime[nn].strftime(fmt)
  646. # special handling of seconds and microseconds:
  647. # strip extra zeros and decimal if possible.
  648. # this is complicated by two factors. 1) we have some level-4 strings
  649. # here (i.e. 03:00, '0.50000', '1.000') 2) we would like to have the
  650. # same number of decimals for each string (i.e. 0.5 and 1.0).
  651. if level >= 5:
  652. trailing_zeros = min(
  653. (len(s) - len(s.rstrip('0')) for s in labels if '.' in s),
  654. default=None)
  655. if trailing_zeros:
  656. for nn in range(len(labels)):
  657. if '.' in labels[nn]:
  658. labels[nn] = labels[nn][:-trailing_zeros].rstrip('.')
  659. if self.show_offset:
  660. # set the offset string:
  661. self.offset_string = tickdatetime[-1].strftime(offsetfmts[level])
  662. return labels
  663. def get_offset(self):
  664. return self.offset_string
  665. def format_data_short(self, value):
  666. return num2date(value, tz=self._tz).strftime('%Y-%m-%d %H:%M:%S')
  667. class AutoDateFormatter(ticker.Formatter):
  668. """
  669. This class attempts to figure out the best format to use. This is
  670. most useful when used with the `AutoDateLocator`.
  671. The AutoDateFormatter has a scale dictionary that maps the scale
  672. of the tick (the distance in days between one major tick) and a
  673. format string. The default looks like this::
  674. self.scaled = {
  675. DAYS_PER_YEAR: rcParams['date.autoformat.year'],
  676. DAYS_PER_MONTH: rcParams['date.autoformat.month'],
  677. 1.0: rcParams['date.autoformat.day'],
  678. 1. / HOURS_PER_DAY: rcParams['date.autoformat.hour'],
  679. 1. / (MINUTES_PER_DAY): rcParams['date.autoformat.minute'],
  680. 1. / (SEC_PER_DAY): rcParams['date.autoformat.second'],
  681. 1. / (MUSECONDS_PER_DAY): rcParams['date.autoformat.microsecond'],
  682. }
  683. The algorithm picks the key in the dictionary that is >= the
  684. current scale and uses that format string. You can customize this
  685. dictionary by doing::
  686. >>> locator = AutoDateLocator()
  687. >>> formatter = AutoDateFormatter(locator)
  688. >>> formatter.scaled[1/(24.*60.)] = '%M:%S' # only show min and sec
  689. A custom `.FuncFormatter` can also be used. The following example shows
  690. how to use a custom format function to strip trailing zeros from decimal
  691. seconds and adds the date to the first ticklabel::
  692. >>> def my_format_function(x, pos=None):
  693. ... x = matplotlib.dates.num2date(x)
  694. ... if pos == 0:
  695. ... fmt = '%D %H:%M:%S.%f'
  696. ... else:
  697. ... fmt = '%H:%M:%S.%f'
  698. ... label = x.strftime(fmt)
  699. ... label = label.rstrip("0")
  700. ... label = label.rstrip(".")
  701. ... return label
  702. >>> from matplotlib.ticker import FuncFormatter
  703. >>> formatter.scaled[1/(24.*60.)] = FuncFormatter(my_format_function)
  704. """
  705. # This can be improved by providing some user-level direction on
  706. # how to choose the best format (precedence, etc...)
  707. # Perhaps a 'struct' that has a field for each time-type where a
  708. # zero would indicate "don't show" and a number would indicate
  709. # "show" with some sort of priority. Same priorities could mean
  710. # show all with the same priority.
  711. # Or more simply, perhaps just a format string for each
  712. # possibility...
  713. def __init__(self, locator, tz=None, defaultfmt='%Y-%m-%d'):
  714. """
  715. Autoformat the date labels. The default format is the one to use
  716. if none of the values in ``self.scaled`` are greater than the unit
  717. returned by ``locator._get_unit()``.
  718. """
  719. self._locator = locator
  720. self._tz = tz
  721. self.defaultfmt = defaultfmt
  722. self._formatter = DateFormatter(self.defaultfmt, tz)
  723. self.scaled = {DAYS_PER_YEAR: rcParams['date.autoformatter.year'],
  724. DAYS_PER_MONTH: rcParams['date.autoformatter.month'],
  725. 1.0: rcParams['date.autoformatter.day'],
  726. 1. / HOURS_PER_DAY: rcParams['date.autoformatter.hour'],
  727. 1. / (MINUTES_PER_DAY):
  728. rcParams['date.autoformatter.minute'],
  729. 1. / (SEC_PER_DAY):
  730. rcParams['date.autoformatter.second'],
  731. 1. / (MUSECONDS_PER_DAY):
  732. rcParams['date.autoformatter.microsecond']}
  733. def _set_locator(self, locator):
  734. self._locator = locator
  735. def __call__(self, x, pos=None):
  736. try:
  737. locator_unit_scale = float(self._locator._get_unit())
  738. except AttributeError:
  739. locator_unit_scale = 1
  740. # Pick the first scale which is greater than the locator unit.
  741. fmt = next((fmt for scale, fmt in sorted(self.scaled.items())
  742. if scale >= locator_unit_scale),
  743. self.defaultfmt)
  744. if isinstance(fmt, str):
  745. self._formatter = DateFormatter(fmt, self._tz)
  746. result = self._formatter(x, pos)
  747. elif callable(fmt):
  748. result = fmt(x, pos)
  749. else:
  750. raise TypeError('Unexpected type passed to {0!r}.'.format(self))
  751. return result
  752. class rrulewrapper:
  753. def __init__(self, freq, tzinfo=None, **kwargs):
  754. kwargs['freq'] = freq
  755. self._base_tzinfo = tzinfo
  756. self._update_rrule(**kwargs)
  757. def set(self, **kwargs):
  758. self._construct.update(kwargs)
  759. self._update_rrule(**self._construct)
  760. def _update_rrule(self, **kwargs):
  761. tzinfo = self._base_tzinfo
  762. # rrule does not play nicely with time zones - especially pytz time
  763. # zones, it's best to use naive zones and attach timezones once the
  764. # datetimes are returned
  765. if 'dtstart' in kwargs:
  766. dtstart = kwargs['dtstart']
  767. if dtstart.tzinfo is not None:
  768. if tzinfo is None:
  769. tzinfo = dtstart.tzinfo
  770. else:
  771. dtstart = dtstart.astimezone(tzinfo)
  772. kwargs['dtstart'] = dtstart.replace(tzinfo=None)
  773. if 'until' in kwargs:
  774. until = kwargs['until']
  775. if until.tzinfo is not None:
  776. if tzinfo is not None:
  777. until = until.astimezone(tzinfo)
  778. else:
  779. raise ValueError('until cannot be aware if dtstart '
  780. 'is naive and tzinfo is None')
  781. kwargs['until'] = until.replace(tzinfo=None)
  782. self._construct = kwargs.copy()
  783. self._tzinfo = tzinfo
  784. self._rrule = rrule(**self._construct)
  785. def _attach_tzinfo(self, dt, tzinfo):
  786. # pytz zones are attached by "localizing" the datetime
  787. if hasattr(tzinfo, 'localize'):
  788. return tzinfo.localize(dt, is_dst=True)
  789. return dt.replace(tzinfo=tzinfo)
  790. def _aware_return_wrapper(self, f, returns_list=False):
  791. """Decorator function that allows rrule methods to handle tzinfo."""
  792. # This is only necessary if we're actually attaching a tzinfo
  793. if self._tzinfo is None:
  794. return f
  795. # All datetime arguments must be naive. If they are not naive, they are
  796. # converted to the _tzinfo zone before dropping the zone.
  797. def normalize_arg(arg):
  798. if isinstance(arg, datetime.datetime) and arg.tzinfo is not None:
  799. if arg.tzinfo is not self._tzinfo:
  800. arg = arg.astimezone(self._tzinfo)
  801. return arg.replace(tzinfo=None)
  802. return arg
  803. def normalize_args(args, kwargs):
  804. args = tuple(normalize_arg(arg) for arg in args)
  805. kwargs = {kw: normalize_arg(arg) for kw, arg in kwargs.items()}
  806. return args, kwargs
  807. # There are two kinds of functions we care about - ones that return
  808. # dates and ones that return lists of dates.
  809. if not returns_list:
  810. def inner_func(*args, **kwargs):
  811. args, kwargs = normalize_args(args, kwargs)
  812. dt = f(*args, **kwargs)
  813. return self._attach_tzinfo(dt, self._tzinfo)
  814. else:
  815. def inner_func(*args, **kwargs):
  816. args, kwargs = normalize_args(args, kwargs)
  817. dts = f(*args, **kwargs)
  818. return [self._attach_tzinfo(dt, self._tzinfo) for dt in dts]
  819. return functools.wraps(f)(inner_func)
  820. def __getattr__(self, name):
  821. if name in self.__dict__:
  822. return self.__dict__[name]
  823. f = getattr(self._rrule, name)
  824. if name in {'after', 'before'}:
  825. return self._aware_return_wrapper(f)
  826. elif name in {'xafter', 'xbefore', 'between'}:
  827. return self._aware_return_wrapper(f, returns_list=True)
  828. else:
  829. return f
  830. def __setstate__(self, state):
  831. self.__dict__.update(state)
  832. class DateLocator(ticker.Locator):
  833. """
  834. Determines the tick locations when plotting dates.
  835. This class is subclassed by other Locators and
  836. is not meant to be used on its own.
  837. """
  838. hms0d = {'byhour': 0, 'byminute': 0, 'bysecond': 0}
  839. def __init__(self, tz=None):
  840. """
  841. *tz* is a :class:`tzinfo` instance.
  842. """
  843. if tz is None:
  844. tz = _get_rc_timezone()
  845. self.tz = tz
  846. def set_tzinfo(self, tz):
  847. """
  848. Set time zone info.
  849. """
  850. self.tz = tz
  851. def datalim_to_dt(self):
  852. """
  853. Convert axis data interval to datetime objects.
  854. """
  855. dmin, dmax = self.axis.get_data_interval()
  856. if dmin > dmax:
  857. dmin, dmax = dmax, dmin
  858. if dmin < 1:
  859. raise ValueError('datalim minimum {} is less than 1 and '
  860. 'is an invalid Matplotlib date value. This often '
  861. 'happens if you pass a non-datetime '
  862. 'value to an axis that has datetime units'
  863. .format(dmin))
  864. return num2date(dmin, self.tz), num2date(dmax, self.tz)
  865. def viewlim_to_dt(self):
  866. """
  867. Converts the view interval to datetime objects.
  868. """
  869. vmin, vmax = self.axis.get_view_interval()
  870. if vmin > vmax:
  871. vmin, vmax = vmax, vmin
  872. if vmin < 1:
  873. raise ValueError('view limit minimum {} is less than 1 and '
  874. 'is an invalid Matplotlib date value. This '
  875. 'often happens if you pass a non-datetime '
  876. 'value to an axis that has datetime units'
  877. .format(vmin))
  878. return num2date(vmin, self.tz), num2date(vmax, self.tz)
  879. def _get_unit(self):
  880. """
  881. Return how many days a unit of the locator is; used for
  882. intelligent autoscaling.
  883. """
  884. return 1
  885. def _get_interval(self):
  886. """
  887. Return the number of units for each tick.
  888. """
  889. return 1
  890. def nonsingular(self, vmin, vmax):
  891. """
  892. Given the proposed upper and lower extent, adjust the range
  893. if it is too close to being singular (i.e. a range of ~0).
  894. """
  895. if not np.isfinite(vmin) or not np.isfinite(vmax):
  896. # Except if there is no data, then use 2000-2010 as default.
  897. return (date2num(datetime.date(2000, 1, 1)),
  898. date2num(datetime.date(2010, 1, 1)))
  899. if vmax < vmin:
  900. vmin, vmax = vmax, vmin
  901. unit = self._get_unit()
  902. interval = self._get_interval()
  903. if abs(vmax - vmin) < 1e-6:
  904. vmin -= 2 * unit * interval
  905. vmax += 2 * unit * interval
  906. return vmin, vmax
  907. class RRuleLocator(DateLocator):
  908. # use the dateutil rrule instance
  909. def __init__(self, o, tz=None):
  910. DateLocator.__init__(self, tz)
  911. self.rule = o
  912. def __call__(self):
  913. # if no data have been set, this will tank with a ValueError
  914. try:
  915. dmin, dmax = self.viewlim_to_dt()
  916. except ValueError:
  917. return []
  918. return self.tick_values(dmin, dmax)
  919. def tick_values(self, vmin, vmax):
  920. delta = relativedelta(vmax, vmin)
  921. # We need to cap at the endpoints of valid datetime
  922. try:
  923. start = vmin - delta
  924. except (ValueError, OverflowError):
  925. start = _from_ordinalf(1.0)
  926. try:
  927. stop = vmax + delta
  928. except (ValueError, OverflowError):
  929. # The magic number!
  930. stop = _from_ordinalf(3652059.9999999)
  931. self.rule.set(dtstart=start, until=stop)
  932. dates = self.rule.between(vmin, vmax, True)
  933. if len(dates) == 0:
  934. return date2num([vmin, vmax])
  935. return self.raise_if_exceeds(date2num(dates))
  936. def _get_unit(self):
  937. """
  938. Return how many days a unit of the locator is; used for
  939. intelligent autoscaling.
  940. """
  941. freq = self.rule._rrule._freq
  942. return self.get_unit_generic(freq)
  943. @staticmethod
  944. def get_unit_generic(freq):
  945. if freq == YEARLY:
  946. return DAYS_PER_YEAR
  947. elif freq == MONTHLY:
  948. return DAYS_PER_MONTH
  949. elif freq == WEEKLY:
  950. return DAYS_PER_WEEK
  951. elif freq == DAILY:
  952. return 1.0
  953. elif freq == HOURLY:
  954. return 1.0 / HOURS_PER_DAY
  955. elif freq == MINUTELY:
  956. return 1.0 / MINUTES_PER_DAY
  957. elif freq == SECONDLY:
  958. return 1.0 / SEC_PER_DAY
  959. else:
  960. # error
  961. return -1 # or should this just return '1'?
  962. def _get_interval(self):
  963. return self.rule._rrule._interval
  964. @cbook.deprecated("3.2")
  965. def autoscale(self):
  966. """
  967. Set the view limits to include the data range.
  968. """
  969. dmin, dmax = self.datalim_to_dt()
  970. delta = relativedelta(dmax, dmin)
  971. # We need to cap at the endpoints of valid datetime
  972. try:
  973. start = dmin - delta
  974. except ValueError:
  975. start = _from_ordinalf(1.0)
  976. try:
  977. stop = dmax + delta
  978. except ValueError:
  979. # The magic number!
  980. stop = _from_ordinalf(3652059.9999999)
  981. self.rule.set(dtstart=start, until=stop)
  982. dmin, dmax = self.datalim_to_dt()
  983. vmin = self.rule.before(dmin, True)
  984. if not vmin:
  985. vmin = dmin
  986. vmax = self.rule.after(dmax, True)
  987. if not vmax:
  988. vmax = dmax
  989. vmin = date2num(vmin)
  990. vmax = date2num(vmax)
  991. return self.nonsingular(vmin, vmax)
  992. class AutoDateLocator(DateLocator):
  993. """
  994. On autoscale, this class picks the best `DateLocator` to set the view
  995. limits and the tick locations.
  996. Attributes
  997. ----------
  998. intervald : dict
  999. Mapping of tick frequencies (a constant from dateutil.rrule) to
  1000. multiples allowed for that ticking. The default looks like this::
  1001. self.intervald = {
  1002. YEARLY : [1, 2, 4, 5, 10, 20, 40, 50, 100, 200, 400, 500,
  1003. 1000, 2000, 4000, 5000, 10000],
  1004. MONTHLY : [1, 2, 3, 4, 6],
  1005. DAILY : [1, 2, 3, 7, 14],
  1006. HOURLY : [1, 2, 3, 4, 6, 12],
  1007. MINUTELY: [1, 5, 10, 15, 30],
  1008. SECONDLY: [1, 5, 10, 15, 30],
  1009. MICROSECONDLY: [1, 2, 5, 10, 20, 50, 100, 200, 500,
  1010. 1000, 2000, 5000, 10000, 20000, 50000,
  1011. 100000, 200000, 500000, 1000000],
  1012. }
  1013. The interval is used to specify multiples that are appropriate for
  1014. the frequency of ticking. For instance, every 7 days is sensible
  1015. for daily ticks, but for minutes/seconds, 15 or 30 make sense.
  1016. You can customize this dictionary by doing::
  1017. locator = AutoDateLocator()
  1018. locator.intervald[HOURLY] = [3] # only show every 3 hours
  1019. """
  1020. def __init__(self, tz=None, minticks=5, maxticks=None,
  1021. interval_multiples=True):
  1022. """
  1023. Parameters
  1024. ----------
  1025. tz : `tzinfo`
  1026. Ticks timezone.
  1027. minticks : int
  1028. The minimum number of ticks desired; controls whether ticks occur
  1029. yearly, monthly, etc.
  1030. maxticks : int
  1031. The maximum number of ticks desired; controls the interval between
  1032. ticks (ticking every other, every 3, etc.). For fine-grained
  1033. control, this can be a dictionary mapping individual rrule
  1034. frequency constants (YEARLY, MONTHLY, etc.) to their own maximum
  1035. number of ticks. This can be used to keep the number of ticks
  1036. appropriate to the format chosen in `AutoDateFormatter`. Any
  1037. frequency not specified in this dictionary is given a default
  1038. value.
  1039. interval_multiples : bool, default: True
  1040. Whether ticks should be chosen to be multiple of the interval,
  1041. locking them to 'nicer' locations. For example, this will force
  1042. the ticks to be at hours 0, 6, 12, 18 when hourly ticking is done
  1043. at 6 hour intervals.
  1044. """
  1045. DateLocator.__init__(self, tz)
  1046. self._locator = YearLocator(tz=tz)
  1047. self._freq = YEARLY
  1048. self._freqs = [YEARLY, MONTHLY, DAILY, HOURLY, MINUTELY,
  1049. SECONDLY, MICROSECONDLY]
  1050. self.minticks = minticks
  1051. self.maxticks = {YEARLY: 11, MONTHLY: 12, DAILY: 11, HOURLY: 12,
  1052. MINUTELY: 11, SECONDLY: 11, MICROSECONDLY: 8}
  1053. if maxticks is not None:
  1054. try:
  1055. self.maxticks.update(maxticks)
  1056. except TypeError:
  1057. # Assume we were given an integer. Use this as the maximum
  1058. # number of ticks for every frequency and create a
  1059. # dictionary for this
  1060. self.maxticks = dict.fromkeys(self._freqs, maxticks)
  1061. self.interval_multiples = interval_multiples
  1062. self.intervald = {
  1063. YEARLY: [1, 2, 4, 5, 10, 20, 40, 50, 100, 200, 400, 500,
  1064. 1000, 2000, 4000, 5000, 10000],
  1065. MONTHLY: [1, 2, 3, 4, 6],
  1066. DAILY: [1, 2, 3, 7, 14, 21],
  1067. HOURLY: [1, 2, 3, 4, 6, 12],
  1068. MINUTELY: [1, 5, 10, 15, 30],
  1069. SECONDLY: [1, 5, 10, 15, 30],
  1070. MICROSECONDLY: [1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000,
  1071. 5000, 10000, 20000, 50000, 100000, 200000, 500000,
  1072. 1000000]}
  1073. if interval_multiples:
  1074. # Swap "3" for "4" in the DAILY list; If we use 3 we get bad
  1075. # tick loc for months w/ 31 days: 1, 4, ..., 28, 31, 1
  1076. # If we use 4 then we get: 1, 5, ... 25, 29, 1
  1077. self.intervald[DAILY] = [1, 2, 4, 7, 14, 21]
  1078. self._byranges = [None, range(1, 13), range(1, 32),
  1079. range(0, 24), range(0, 60), range(0, 60), None]
  1080. def __call__(self):
  1081. 'Return the locations of the ticks'
  1082. self.refresh()
  1083. return self._locator()
  1084. def tick_values(self, vmin, vmax):
  1085. return self.get_locator(vmin, vmax).tick_values(vmin, vmax)
  1086. def nonsingular(self, vmin, vmax):
  1087. # whatever is thrown at us, we can scale the unit.
  1088. # But default nonsingular date plots at an ~4 year period.
  1089. if not np.isfinite(vmin) or not np.isfinite(vmax):
  1090. # Except if there is no data, then use 2000-2010 as default.
  1091. return (date2num(datetime.date(2000, 1, 1)),
  1092. date2num(datetime.date(2010, 1, 1)))
  1093. if vmax < vmin:
  1094. vmin, vmax = vmax, vmin
  1095. if vmin == vmax:
  1096. vmin = vmin - DAYS_PER_YEAR * 2
  1097. vmax = vmax + DAYS_PER_YEAR * 2
  1098. return vmin, vmax
  1099. def set_axis(self, axis):
  1100. DateLocator.set_axis(self, axis)
  1101. self._locator.set_axis(axis)
  1102. def refresh(self):
  1103. # docstring inherited
  1104. dmin, dmax = self.viewlim_to_dt()
  1105. self._locator = self.get_locator(dmin, dmax)
  1106. def _get_unit(self):
  1107. if self._freq in [MICROSECONDLY]:
  1108. return 1. / MUSECONDS_PER_DAY
  1109. else:
  1110. return RRuleLocator.get_unit_generic(self._freq)
  1111. @cbook.deprecated("3.2")
  1112. def autoscale(self):
  1113. 'Try to choose the view limits intelligently.'
  1114. dmin, dmax = self.datalim_to_dt()
  1115. self._locator = self.get_locator(dmin, dmax)
  1116. return self._locator.autoscale()
  1117. def get_locator(self, dmin, dmax):
  1118. 'Pick the best locator based on a distance.'
  1119. delta = relativedelta(dmax, dmin)
  1120. tdelta = dmax - dmin
  1121. # take absolute difference
  1122. if dmin > dmax:
  1123. delta = -delta
  1124. tdelta = -tdelta
  1125. # The following uses a mix of calls to relativedelta and timedelta
  1126. # methods because there is incomplete overlap in the functionality of
  1127. # these similar functions, and it's best to avoid doing our own math
  1128. # whenever possible.
  1129. numYears = float(delta.years)
  1130. numMonths = numYears * MONTHS_PER_YEAR + delta.months
  1131. numDays = tdelta.days # Avoids estimates of days/month, days/year
  1132. numHours = numDays * HOURS_PER_DAY + delta.hours
  1133. numMinutes = numHours * MIN_PER_HOUR + delta.minutes
  1134. numSeconds = np.floor(tdelta.total_seconds())
  1135. numMicroseconds = np.floor(tdelta.total_seconds() * 1e6)
  1136. nums = [numYears, numMonths, numDays, numHours, numMinutes,
  1137. numSeconds, numMicroseconds]
  1138. use_rrule_locator = [True] * 6 + [False]
  1139. # Default setting of bymonth, etc. to pass to rrule
  1140. # [unused (for year), bymonth, bymonthday, byhour, byminute,
  1141. # bysecond, unused (for microseconds)]
  1142. byranges = [None, 1, 1, 0, 0, 0, None]
  1143. # Loop over all the frequencies and try to find one that gives at
  1144. # least a minticks tick positions. Once this is found, look for
  1145. # an interval from an list specific to that frequency that gives no
  1146. # more than maxticks tick positions. Also, set up some ranges
  1147. # (bymonth, etc.) as appropriate to be passed to rrulewrapper.
  1148. for i, (freq, num) in enumerate(zip(self._freqs, nums)):
  1149. # If this particular frequency doesn't give enough ticks, continue
  1150. if num < self.minticks:
  1151. # Since we're not using this particular frequency, set
  1152. # the corresponding by_ to None so the rrule can act as
  1153. # appropriate
  1154. byranges[i] = None
  1155. continue
  1156. # Find the first available interval that doesn't give too many
  1157. # ticks
  1158. for interval in self.intervald[freq]:
  1159. if num <= interval * (self.maxticks[freq] - 1):
  1160. break
  1161. else:
  1162. # We went through the whole loop without breaking, default to
  1163. # the last interval in the list and raise a warning
  1164. cbook._warn_external(
  1165. f"AutoDateLocator was unable to pick an appropriate "
  1166. f"interval for this date range. It may be necessary to "
  1167. f"add an interval value to the AutoDateLocator's "
  1168. f"intervald dictionary. Defaulting to {interval}.")
  1169. # Set some parameters as appropriate
  1170. self._freq = freq
  1171. if self._byranges[i] and self.interval_multiples:
  1172. byranges[i] = self._byranges[i][::interval]
  1173. if i in (DAILY, WEEKLY):
  1174. if interval == 14:
  1175. # just make first and 15th. Avoids 30th.
  1176. byranges[i] = [1, 15]
  1177. elif interval == 7:
  1178. byranges[i] = [1, 8, 15, 22]
  1179. interval = 1
  1180. else:
  1181. byranges[i] = self._byranges[i]
  1182. break
  1183. else:
  1184. raise ValueError('No sensible date limit could be found in the '
  1185. 'AutoDateLocator.')
  1186. if (freq == YEARLY) and self.interval_multiples:
  1187. locator = YearLocator(interval, tz=self.tz)
  1188. elif use_rrule_locator[i]:
  1189. _, bymonth, bymonthday, byhour, byminute, bysecond, _ = byranges
  1190. rrule = rrulewrapper(self._freq, interval=interval,
  1191. dtstart=dmin, until=dmax,
  1192. bymonth=bymonth, bymonthday=bymonthday,
  1193. byhour=byhour, byminute=byminute,
  1194. bysecond=bysecond)
  1195. locator = RRuleLocator(rrule, self.tz)
  1196. else:
  1197. locator = MicrosecondLocator(interval, tz=self.tz)
  1198. if dmin.year > 20 and interval < 1000:
  1199. cbook._warn_external(
  1200. 'Plotting microsecond time intervals is not well '
  1201. 'supported; please see the MicrosecondLocator '
  1202. 'documentation for details.')
  1203. locator.set_axis(self.axis)
  1204. if self.axis is not None:
  1205. locator.set_view_interval(*self.axis.get_view_interval())
  1206. locator.set_data_interval(*self.axis.get_data_interval())
  1207. return locator
  1208. class YearLocator(DateLocator):
  1209. """
  1210. Make ticks on a given day of each year that is a multiple of base.
  1211. Examples::
  1212. # Tick every year on Jan 1st
  1213. locator = YearLocator()
  1214. # Tick every 5 years on July 4th
  1215. locator = YearLocator(5, month=7, day=4)
  1216. """
  1217. def __init__(self, base=1, month=1, day=1, tz=None):
  1218. """
  1219. Mark years that are multiple of base on a given month and day
  1220. (default jan 1).
  1221. """
  1222. DateLocator.__init__(self, tz)
  1223. self.base = ticker._Edge_integer(base, 0)
  1224. self.replaced = {'month': month,
  1225. 'day': day,
  1226. 'hour': 0,
  1227. 'minute': 0,
  1228. 'second': 0,
  1229. }
  1230. if not hasattr(tz, 'localize'):
  1231. # if tz is pytz, we need to do this w/ the localize fcn,
  1232. # otherwise datetime.replace works fine...
  1233. self.replaced['tzinfo'] = tz
  1234. def __call__(self):
  1235. # if no data have been set, this will tank with a ValueError
  1236. try:
  1237. dmin, dmax = self.viewlim_to_dt()
  1238. except ValueError:
  1239. return []
  1240. return self.tick_values(dmin, dmax)
  1241. def tick_values(self, vmin, vmax):
  1242. ymin = self.base.le(vmin.year) * self.base.step
  1243. ymax = self.base.ge(vmax.year) * self.base.step
  1244. vmin = vmin.replace(year=ymin, **self.replaced)
  1245. if hasattr(self.tz, 'localize'):
  1246. # look after pytz
  1247. if not vmin.tzinfo:
  1248. vmin = self.tz.localize(vmin, is_dst=True)
  1249. ticks = [vmin]
  1250. while True:
  1251. dt = ticks[-1]
  1252. if dt.year >= ymax:
  1253. return date2num(ticks)
  1254. year = dt.year + self.base.step
  1255. dt = dt.replace(year=year, **self.replaced)
  1256. if hasattr(self.tz, 'localize'):
  1257. # look after pytz
  1258. if not dt.tzinfo:
  1259. dt = self.tz.localize(dt, is_dst=True)
  1260. ticks.append(dt)
  1261. @cbook.deprecated("3.2")
  1262. def autoscale(self):
  1263. """
  1264. Set the view limits to include the data range.
  1265. """
  1266. dmin, dmax = self.datalim_to_dt()
  1267. ymin = self.base.le(dmin.year)
  1268. ymax = self.base.ge(dmax.year)
  1269. vmin = dmin.replace(year=ymin, **self.replaced)
  1270. vmin = vmin.astimezone(self.tz)
  1271. vmax = dmax.replace(year=ymax, **self.replaced)
  1272. vmax = vmax.astimezone(self.tz)
  1273. vmin = date2num(vmin)
  1274. vmax = date2num(vmax)
  1275. return self.nonsingular(vmin, vmax)
  1276. class MonthLocator(RRuleLocator):
  1277. """
  1278. Make ticks on occurrences of each month, e.g., 1, 3, 12.
  1279. """
  1280. def __init__(self, bymonth=None, bymonthday=1, interval=1, tz=None):
  1281. """
  1282. Mark every month in *bymonth*; *bymonth* can be an int or
  1283. sequence. Default is ``range(1, 13)``, i.e. every month.
  1284. *interval* is the interval between each iteration. For
  1285. example, if ``interval=2``, mark every second occurrence.
  1286. """
  1287. if bymonth is None:
  1288. bymonth = range(1, 13)
  1289. elif isinstance(bymonth, np.ndarray):
  1290. # This fixes a bug in dateutil <= 2.3 which prevents the use of
  1291. # numpy arrays in (among other things) the bymonthday, byweekday
  1292. # and bymonth parameters.
  1293. bymonth = [x.item() for x in bymonth.astype(int)]
  1294. rule = rrulewrapper(MONTHLY, bymonth=bymonth, bymonthday=bymonthday,
  1295. interval=interval, **self.hms0d)
  1296. RRuleLocator.__init__(self, rule, tz)
  1297. class WeekdayLocator(RRuleLocator):
  1298. """
  1299. Make ticks on occurrences of each weekday.
  1300. """
  1301. def __init__(self, byweekday=1, interval=1, tz=None):
  1302. """
  1303. Mark every weekday in *byweekday*; *byweekday* can be a number or
  1304. sequence.
  1305. Elements of *byweekday* must be one of MO, TU, WE, TH, FR, SA,
  1306. SU, the constants from :mod:`dateutil.rrule`, which have been
  1307. imported into the :mod:`matplotlib.dates` namespace.
  1308. *interval* specifies the number of weeks to skip. For example,
  1309. ``interval=2`` plots every second week.
  1310. """
  1311. if isinstance(byweekday, np.ndarray):
  1312. # This fixes a bug in dateutil <= 2.3 which prevents the use of
  1313. # numpy arrays in (among other things) the bymonthday, byweekday
  1314. # and bymonth parameters.
  1315. [x.item() for x in byweekday.astype(int)]
  1316. rule = rrulewrapper(DAILY, byweekday=byweekday,
  1317. interval=interval, **self.hms0d)
  1318. RRuleLocator.__init__(self, rule, tz)
  1319. class DayLocator(RRuleLocator):
  1320. """
  1321. Make ticks on occurrences of each day of the month. For example,
  1322. 1, 15, 30.
  1323. """
  1324. def __init__(self, bymonthday=None, interval=1, tz=None):
  1325. """
  1326. Mark every day in *bymonthday*; *bymonthday* can be an int or sequence.
  1327. Default is to tick every day of the month: ``bymonthday=range(1, 32)``.
  1328. """
  1329. if interval != int(interval) or interval < 1:
  1330. raise ValueError("interval must be an integer greater than 0")
  1331. if bymonthday is None:
  1332. bymonthday = range(1, 32)
  1333. elif isinstance(bymonthday, np.ndarray):
  1334. # This fixes a bug in dateutil <= 2.3 which prevents the use of
  1335. # numpy arrays in (among other things) the bymonthday, byweekday
  1336. # and bymonth parameters.
  1337. bymonthday = [x.item() for x in bymonthday.astype(int)]
  1338. rule = rrulewrapper(DAILY, bymonthday=bymonthday,
  1339. interval=interval, **self.hms0d)
  1340. RRuleLocator.__init__(self, rule, tz)
  1341. class HourLocator(RRuleLocator):
  1342. """
  1343. Make ticks on occurrences of each hour.
  1344. """
  1345. def __init__(self, byhour=None, interval=1, tz=None):
  1346. """
  1347. Mark every hour in *byhour*; *byhour* can be an int or sequence.
  1348. Default is to tick every hour: ``byhour=range(24)``
  1349. *interval* is the interval between each iteration. For
  1350. example, if ``interval=2``, mark every second occurrence.
  1351. """
  1352. if byhour is None:
  1353. byhour = range(24)
  1354. rule = rrulewrapper(HOURLY, byhour=byhour, interval=interval,
  1355. byminute=0, bysecond=0)
  1356. RRuleLocator.__init__(self, rule, tz)
  1357. class MinuteLocator(RRuleLocator):
  1358. """
  1359. Make ticks on occurrences of each minute.
  1360. """
  1361. def __init__(self, byminute=None, interval=1, tz=None):
  1362. """
  1363. Mark every minute in *byminute*; *byminute* can be an int or
  1364. sequence. Default is to tick every minute: ``byminute=range(60)``
  1365. *interval* is the interval between each iteration. For
  1366. example, if ``interval=2``, mark every second occurrence.
  1367. """
  1368. if byminute is None:
  1369. byminute = range(60)
  1370. rule = rrulewrapper(MINUTELY, byminute=byminute, interval=interval,
  1371. bysecond=0)
  1372. RRuleLocator.__init__(self, rule, tz)
  1373. class SecondLocator(RRuleLocator):
  1374. """
  1375. Make ticks on occurrences of each second.
  1376. """
  1377. def __init__(self, bysecond=None, interval=1, tz=None):
  1378. """
  1379. Mark every second in *bysecond*; *bysecond* can be an int or
  1380. sequence. Default is to tick every second: ``bysecond = range(60)``
  1381. *interval* is the interval between each iteration. For
  1382. example, if ``interval=2``, mark every second occurrence.
  1383. """
  1384. if bysecond is None:
  1385. bysecond = range(60)
  1386. rule = rrulewrapper(SECONDLY, bysecond=bysecond, interval=interval)
  1387. RRuleLocator.__init__(self, rule, tz)
  1388. class MicrosecondLocator(DateLocator):
  1389. """
  1390. Make ticks on regular intervals of one or more microsecond(s).
  1391. .. note::
  1392. Due to the floating point representation of time in days since
  1393. 0001-01-01 UTC (plus 1), plotting data with microsecond time
  1394. resolution does not work well with current dates.
  1395. If you want microsecond resolution time plots, it is strongly
  1396. recommended to use floating point seconds, not datetime-like
  1397. time representation.
  1398. If you really must use datetime.datetime() or similar and still
  1399. need microsecond precision, your only chance is to use very
  1400. early years; using year 0001 is recommended.
  1401. """
  1402. def __init__(self, interval=1, tz=None):
  1403. """
  1404. *interval* is the interval between each iteration. For
  1405. example, if ``interval=2``, mark every second microsecond.
  1406. """
  1407. self._interval = interval
  1408. self._wrapped_locator = ticker.MultipleLocator(interval)
  1409. self.tz = tz
  1410. def set_axis(self, axis):
  1411. self._wrapped_locator.set_axis(axis)
  1412. return DateLocator.set_axis(self, axis)
  1413. def set_view_interval(self, vmin, vmax):
  1414. self._wrapped_locator.set_view_interval(vmin, vmax)
  1415. return DateLocator.set_view_interval(self, vmin, vmax)
  1416. def set_data_interval(self, vmin, vmax):
  1417. self._wrapped_locator.set_data_interval(vmin, vmax)
  1418. return DateLocator.set_data_interval(self, vmin, vmax)
  1419. def __call__(self):
  1420. # if no data have been set, this will tank with a ValueError
  1421. try:
  1422. dmin, dmax = self.viewlim_to_dt()
  1423. except ValueError:
  1424. return []
  1425. return self.tick_values(dmin, dmax)
  1426. def tick_values(self, vmin, vmax):
  1427. nmin, nmax = date2num((vmin, vmax))
  1428. nmin *= MUSECONDS_PER_DAY
  1429. nmax *= MUSECONDS_PER_DAY
  1430. ticks = self._wrapped_locator.tick_values(nmin, nmax)
  1431. ticks = [tick / MUSECONDS_PER_DAY for tick in ticks]
  1432. return ticks
  1433. def _get_unit(self):
  1434. """
  1435. Return how many days a unit of the locator is; used for
  1436. intelligent autoscaling.
  1437. """
  1438. return 1. / MUSECONDS_PER_DAY
  1439. def _get_interval(self):
  1440. """
  1441. Return the number of units for each tick.
  1442. """
  1443. return self._interval
  1444. def epoch2num(e):
  1445. """
  1446. Convert an epoch or sequence of epochs to the new date format,
  1447. that is days since 0001.
  1448. """
  1449. return EPOCH_OFFSET + np.asarray(e) / SEC_PER_DAY
  1450. def num2epoch(d):
  1451. """
  1452. Convert days since 0001 to epoch. *d* can be a number or sequence.
  1453. """
  1454. return (np.asarray(d) - EPOCH_OFFSET) * SEC_PER_DAY
  1455. @cbook.deprecated("3.2")
  1456. def mx2num(mxdates):
  1457. """
  1458. Convert mx :class:`datetime` instance (or sequence of mx
  1459. instances) to the new date format.
  1460. """
  1461. scalar = False
  1462. if not np.iterable(mxdates):
  1463. scalar = True
  1464. mxdates = [mxdates]
  1465. ret = epoch2num([m.ticks() for m in mxdates])
  1466. if scalar:
  1467. return ret[0]
  1468. else:
  1469. return ret
  1470. def date_ticker_factory(span, tz=None, numticks=5):
  1471. """
  1472. Create a date locator with *numticks* (approx) and a date formatter
  1473. for *span* in days. Return value is (locator, formatter).
  1474. """
  1475. if span == 0:
  1476. span = 1 / HOURS_PER_DAY
  1477. mins = span * MINUTES_PER_DAY
  1478. hrs = span * HOURS_PER_DAY
  1479. days = span
  1480. wks = span / DAYS_PER_WEEK
  1481. months = span / DAYS_PER_MONTH # Approx
  1482. years = span / DAYS_PER_YEAR # Approx
  1483. if years > numticks:
  1484. locator = YearLocator(int(years / numticks), tz=tz) # define
  1485. fmt = '%Y'
  1486. elif months > numticks:
  1487. locator = MonthLocator(tz=tz)
  1488. fmt = '%b %Y'
  1489. elif wks > numticks:
  1490. locator = WeekdayLocator(tz=tz)
  1491. fmt = '%a, %b %d'
  1492. elif days > numticks:
  1493. locator = DayLocator(interval=math.ceil(days / numticks), tz=tz)
  1494. fmt = '%b %d'
  1495. elif hrs > numticks:
  1496. locator = HourLocator(interval=math.ceil(hrs / numticks), tz=tz)
  1497. fmt = '%H:%M\n%b %d'
  1498. elif mins > numticks:
  1499. locator = MinuteLocator(interval=math.ceil(mins / numticks), tz=tz)
  1500. fmt = '%H:%M:%S'
  1501. else:
  1502. locator = MinuteLocator(tz=tz)
  1503. fmt = '%H:%M:%S'
  1504. formatter = DateFormatter(fmt, tz=tz)
  1505. return locator, formatter
  1506. @cbook.deprecated("3.1")
  1507. def seconds(s):
  1508. """
  1509. Return seconds as days.
  1510. """
  1511. return s / SEC_PER_DAY
  1512. @cbook.deprecated("3.1")
  1513. def minutes(m):
  1514. """
  1515. Return minutes as days.
  1516. """
  1517. return m / MINUTES_PER_DAY
  1518. @cbook.deprecated("3.1")
  1519. def hours(h):
  1520. """
  1521. Return hours as days.
  1522. """
  1523. return h / HOURS_PER_DAY
  1524. @cbook.deprecated("3.1")
  1525. def weeks(w):
  1526. """
  1527. Return weeks as days.
  1528. """
  1529. return w * DAYS_PER_WEEK
  1530. class DateConverter(units.ConversionInterface):
  1531. """
  1532. Converter for `datetime.date` and `datetime.datetime` data, or for
  1533. date/time data represented as it would be converted by `date2num`.
  1534. The 'unit' tag for such data is None or a tzinfo instance.
  1535. """
  1536. @staticmethod
  1537. def axisinfo(unit, axis):
  1538. """
  1539. Return the `~matplotlib.units.AxisInfo` for *unit*.
  1540. *unit* is a tzinfo instance or None.
  1541. The *axis* argument is required but not used.
  1542. """
  1543. tz = unit
  1544. majloc = AutoDateLocator(tz=tz)
  1545. majfmt = AutoDateFormatter(majloc, tz=tz)
  1546. datemin = datetime.date(2000, 1, 1)
  1547. datemax = datetime.date(2010, 1, 1)
  1548. return units.AxisInfo(majloc=majloc, majfmt=majfmt, label='',
  1549. default_limits=(datemin, datemax))
  1550. @staticmethod
  1551. def convert(value, unit, axis):
  1552. """
  1553. If *value* is not already a number or sequence of numbers, convert it
  1554. with `date2num`.
  1555. The *unit* and *axis* arguments are not used.
  1556. """
  1557. return date2num(value)
  1558. @staticmethod
  1559. def default_units(x, axis):
  1560. """
  1561. Return the tzinfo instance of *x* or of its first element, or None
  1562. """
  1563. if isinstance(x, np.ndarray):
  1564. x = x.ravel()
  1565. try:
  1566. x = cbook.safe_first_element(x)
  1567. except (TypeError, StopIteration):
  1568. pass
  1569. try:
  1570. return x.tzinfo
  1571. except AttributeError:
  1572. pass
  1573. return None
  1574. class ConciseDateConverter(DateConverter):
  1575. # docstring inherited
  1576. def __init__(self, formats=None, zero_formats=None, offset_formats=None,
  1577. show_offset=True):
  1578. self._formats = formats
  1579. self._zero_formats = zero_formats
  1580. self._offset_formats = offset_formats
  1581. self._show_offset = show_offset
  1582. super().__init__()
  1583. def axisinfo(self, unit, axis):
  1584. # docstring inherited
  1585. tz = unit
  1586. majloc = AutoDateLocator(tz=tz)
  1587. majfmt = ConciseDateFormatter(majloc, tz=tz, formats=self._formats,
  1588. zero_formats=self._zero_formats,
  1589. offset_formats=self._offset_formats,
  1590. show_offset=self._show_offset)
  1591. datemin = datetime.date(2000, 1, 1)
  1592. datemax = datetime.date(2010, 1, 1)
  1593. return units.AxisInfo(majloc=majloc, majfmt=majfmt, label='',
  1594. default_limits=(datemin, datemax))
  1595. units.registry[np.datetime64] = DateConverter()
  1596. units.registry[datetime.date] = DateConverter()
  1597. units.registry[datetime.datetime] = DateConverter()