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

1825 lines
60 KiB

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