Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/matplotlib/dates.py : 21%

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