1955 lines
		
	
	
	
		
			66 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1955 lines
		
	
	
	
		
			66 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
"""
 | 
						|
Matplotlib provides sophisticated date plotting capabilities, standing on the
 | 
						|
shoulders of python :mod:`datetime` and the add-on module :mod:`dateutil`.
 | 
						|
 | 
						|
.. _date-format:
 | 
						|
 | 
						|
Matplotlib date format
 | 
						|
----------------------
 | 
						|
 | 
						|
Matplotlib represents dates using floating point numbers specifying the number
 | 
						|
of days since a default epoch of 1970-01-01 UTC; for example,
 | 
						|
1970-01-01, 06:00 is the floating point number 0.25. The formatters and
 | 
						|
locators require the use of `datetime.datetime` objects, so only dates between
 | 
						|
year 0001 and 9999 can be represented.  Microsecond precision
 | 
						|
is achievable for (approximately) 70 years on either side of the epoch, and
 | 
						|
20 microseconds for the rest of the allowable range of dates (year 0001 to
 | 
						|
9999). The epoch can be changed at import time via `.dates.set_epoch` or
 | 
						|
:rc:`dates.epoch` to other dates if necessary; see
 | 
						|
:doc:`/gallery/ticks_and_spines/date_precision_and_epochs` for a discussion.
 | 
						|
 | 
						|
.. note::
 | 
						|
 | 
						|
   Before Matplotlib 3.3, the epoch was 0000-12-31 which lost modern
 | 
						|
   microsecond precision and also made the default axis limit of 0 an invalid
 | 
						|
   datetime.  In 3.3 the epoch was changed as above.  To convert old
 | 
						|
   ordinal floats to the new epoch, users can do::
 | 
						|
 | 
						|
     new_ordinal = old_ordinal + mdates.date2num(np.datetime64('0000-12-31'))
 | 
						|
 | 
						|
 | 
						|
There are a number of helper functions to convert between :mod:`datetime`
 | 
						|
objects and Matplotlib dates:
 | 
						|
 | 
						|
.. currentmodule:: matplotlib.dates
 | 
						|
 | 
						|
.. autosummary::
 | 
						|
   :nosignatures:
 | 
						|
 | 
						|
   datestr2num
 | 
						|
   date2num
 | 
						|
   num2date
 | 
						|
   num2timedelta
 | 
						|
   drange
 | 
						|
   set_epoch
 | 
						|
   get_epoch
 | 
						|
 | 
						|
.. note::
 | 
						|
 | 
						|
   Like Python's `datetime.datetime`, Matplotlib uses the Gregorian calendar
 | 
						|
   for all conversions between dates and floating point numbers. This practice
 | 
						|
   is not universal, and calendar differences can cause confusing
 | 
						|
   differences between what Python and Matplotlib give as the number of days
 | 
						|
   since 0001-01-01 and what other software and databases yield.  For
 | 
						|
   example, the US Naval Observatory uses a calendar that switches
 | 
						|
   from Julian to Gregorian in October, 1582.  Hence, using their
 | 
						|
   calculator, the number of days between 0001-01-01 and 2006-04-01 is
 | 
						|
   732403, whereas using the Gregorian calendar via the datetime
 | 
						|
   module we find::
 | 
						|
 | 
						|
     In [1]: date(2006, 4, 1).toordinal() - date(1, 1, 1).toordinal()
 | 
						|
     Out[1]: 732401
 | 
						|
 | 
						|
All the Matplotlib date converters, tickers and formatters are timezone aware.
 | 
						|
If no explicit timezone is provided, :rc:`timezone` is assumed.  If you want to
 | 
						|
use a custom time zone, pass a `datetime.tzinfo` instance with the tz keyword
 | 
						|
argument to `num2date`, `~.Axes.plot_date`, and any custom date tickers or
 | 
						|
locators you create.
 | 
						|
 | 
						|
A wide range of specific and general purpose date tick locators and
 | 
						|
formatters are provided in this module.  See
 | 
						|
:mod:`matplotlib.ticker` for general information on tick locators
 | 
						|
and formatters.  These are described below.
 | 
						|
 | 
						|
The dateutil_ module provides additional code to handle date ticking, making it
 | 
						|
easy to place ticks on any kinds of dates.  See examples below.
 | 
						|
 | 
						|
.. _dateutil: https://dateutil.readthedocs.io
 | 
						|
 | 
						|
Date tickers
 | 
						|
------------
 | 
						|
 | 
						|
Most of the date tickers can locate single or multiple values.  For example::
 | 
						|
 | 
						|
    # import constants for the days of the week
 | 
						|
    from matplotlib.dates import MO, TU, WE, TH, FR, SA, SU
 | 
						|
 | 
						|
    # tick on mondays every week
 | 
						|
    loc = WeekdayLocator(byweekday=MO, tz=tz)
 | 
						|
 | 
						|
    # tick on mondays and saturdays
 | 
						|
    loc = WeekdayLocator(byweekday=(MO, SA))
 | 
						|
 | 
						|
In addition, most of the constructors take an interval argument::
 | 
						|
 | 
						|
    # tick on mondays every second week
 | 
						|
    loc = WeekdayLocator(byweekday=MO, interval=2)
 | 
						|
 | 
						|
The rrule locator allows completely general date ticking::
 | 
						|
 | 
						|
    # tick every 5th easter
 | 
						|
    rule = rrulewrapper(YEARLY, byeaster=1, interval=5)
 | 
						|
    loc = RRuleLocator(rule)
 | 
						|
 | 
						|
The available date tickers are:
 | 
						|
 | 
						|
* `MicrosecondLocator`: Locate microseconds.
 | 
						|
 | 
						|
* `SecondLocator`: Locate seconds.
 | 
						|
 | 
						|
* `MinuteLocator`: Locate minutes.
 | 
						|
 | 
						|
* `HourLocator`: Locate hours.
 | 
						|
 | 
						|
* `DayLocator`: Locate specified days of the month.
 | 
						|
 | 
						|
* `WeekdayLocator`: Locate days of the week, e.g., MO, TU.
 | 
						|
 | 
						|
* `MonthLocator`: Locate months, e.g., 7 for July.
 | 
						|
 | 
						|
* `YearLocator`: Locate years that are multiples of base.
 | 
						|
 | 
						|
* `RRuleLocator`: Locate using a `matplotlib.dates.rrulewrapper`.
 | 
						|
  `.rrulewrapper` is a simple wrapper around dateutil_'s `dateutil.rrule` which
 | 
						|
  allow almost arbitrary date tick specifications.  See :doc:`rrule example
 | 
						|
  </gallery/ticks_and_spines/date_demo_rrule>`.
 | 
						|
 | 
						|
* `AutoDateLocator`: On autoscale, this class picks the best `DateLocator`
 | 
						|
  (e.g., `RRuleLocator`) to set the view limits and the tick locations.  If
 | 
						|
  called with ``interval_multiples=True`` it will make ticks line up with
 | 
						|
  sensible multiples of the tick intervals.  E.g. if the interval is 4 hours,
 | 
						|
  it will pick hours 0, 4, 8, etc as ticks.  This behaviour is not guaranteed
 | 
						|
  by default.
 | 
						|
 | 
						|
Date formatters
 | 
						|
---------------
 | 
						|
 | 
						|
The available date formatters are:
 | 
						|
 | 
						|
* `AutoDateFormatter`: attempts to figure out the best format to use.  This is
 | 
						|
  most useful when used with the `AutoDateLocator`.
 | 
						|
 | 
						|
* `ConciseDateFormatter`: also attempts to figure out the best format to use,
 | 
						|
  and to make the format as compact as possible while still having complete
 | 
						|
  date information.  This is most useful when used with the `AutoDateLocator`.
 | 
						|
 | 
						|
* `DateFormatter`: use `~datetime.datetime.strftime` format strings.
 | 
						|
 | 
						|
* `IndexDateFormatter`: date plots with implicit *x* indexing.
 | 
						|
"""
 | 
						|
 | 
						|
import datetime
 | 
						|
import functools
 | 
						|
import logging
 | 
						|
import math
 | 
						|
import re
 | 
						|
 | 
						|
from dateutil.rrule import (rrule, MO, TU, WE, TH, FR, SA, SU, YEARLY,
 | 
						|
                            MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY,
 | 
						|
                            SECONDLY)
 | 
						|
from dateutil.relativedelta import relativedelta
 | 
						|
import dateutil.parser
 | 
						|
import dateutil.tz
 | 
						|
import numpy as np
 | 
						|
 | 
						|
import matplotlib
 | 
						|
import matplotlib.units as units
 | 
						|
import matplotlib.cbook as cbook
 | 
						|
import matplotlib.ticker as ticker
 | 
						|
 | 
						|
__all__ = ('datestr2num', 'date2num', 'num2date', 'num2timedelta', 'drange',
 | 
						|
           'epoch2num', 'num2epoch', 'mx2num', 'set_epoch',
 | 
						|
           'get_epoch', 'DateFormatter',
 | 
						|
           'ConciseDateFormatter', 'IndexDateFormatter', 'AutoDateFormatter',
 | 
						|
           'DateLocator', 'RRuleLocator', 'AutoDateLocator', 'YearLocator',
 | 
						|
           'MonthLocator', 'WeekdayLocator',
 | 
						|
           'DayLocator', 'HourLocator', 'MinuteLocator',
 | 
						|
           'SecondLocator', 'MicrosecondLocator',
 | 
						|
           'rrule', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU',
 | 
						|
           'YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY',
 | 
						|
           'HOURLY', 'MINUTELY', 'SECONDLY', 'MICROSECONDLY', 'relativedelta',
 | 
						|
           'DateConverter', 'ConciseDateConverter')
 | 
						|
 | 
						|
 | 
						|
_log = logging.getLogger(__name__)
 | 
						|
UTC = datetime.timezone.utc
 | 
						|
 | 
						|
 | 
						|
def _get_rc_timezone():
 | 
						|
    """Retrieve the preferred timezone from the rcParams dictionary."""
 | 
						|
    s = matplotlib.rcParams['timezone']
 | 
						|
    if s == 'UTC':
 | 
						|
        return UTC
 | 
						|
    return dateutil.tz.gettz(s)
 | 
						|
 | 
						|
 | 
						|
"""
 | 
						|
Time-related constants.
 | 
						|
"""
 | 
						|
EPOCH_OFFSET = float(datetime.datetime(1970, 1, 1).toordinal())
 | 
						|
# EPOCH_OFFSET is not used by matplotlib
 | 
						|
JULIAN_OFFSET = 1721424.5  # Julian date at 0000-12-31
 | 
						|
# note that the Julian day epoch is achievable w/
 | 
						|
# np.datetime64('-4713-11-24T12:00:00'); datetime64 is proleptic
 | 
						|
# Gregorian and BC has a one-year offset.  So
 | 
						|
# np.datetime64('0000-12-31') - np.datetime64('-4713-11-24T12:00') = 1721424.5
 | 
						|
# Ref: https://en.wikipedia.org/wiki/Julian_day
 | 
						|
MICROSECONDLY = SECONDLY + 1
 | 
						|
HOURS_PER_DAY = 24.
 | 
						|
MIN_PER_HOUR = 60.
 | 
						|
SEC_PER_MIN = 60.
 | 
						|
MONTHS_PER_YEAR = 12.
 | 
						|
 | 
						|
DAYS_PER_WEEK = 7.
 | 
						|
DAYS_PER_MONTH = 30.
 | 
						|
DAYS_PER_YEAR = 365.0
 | 
						|
 | 
						|
MINUTES_PER_DAY = MIN_PER_HOUR * HOURS_PER_DAY
 | 
						|
 | 
						|
SEC_PER_HOUR = SEC_PER_MIN * MIN_PER_HOUR
 | 
						|
SEC_PER_DAY = SEC_PER_HOUR * HOURS_PER_DAY
 | 
						|
SEC_PER_WEEK = SEC_PER_DAY * DAYS_PER_WEEK
 | 
						|
 | 
						|
MUSECONDS_PER_DAY = 1e6 * SEC_PER_DAY
 | 
						|
 | 
						|
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY = (
 | 
						|
    MO, TU, WE, TH, FR, SA, SU)
 | 
						|
WEEKDAYS = (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY)
 | 
						|
 | 
						|
# default epoch: passed to np.datetime64...
 | 
						|
_epoch = None
 | 
						|
 | 
						|
 | 
						|
def _reset_epoch_test_example():
 | 
						|
    """
 | 
						|
    Reset the Matplotlib date epoch so it can be set again.
 | 
						|
 | 
						|
    Only for use in tests and examples.
 | 
						|
    """
 | 
						|
    global _epoch
 | 
						|
    _epoch = None
 | 
						|
 | 
						|
 | 
						|
def set_epoch(epoch):
 | 
						|
    """
 | 
						|
    Set the epoch (origin for dates) for datetime calculations.
 | 
						|
 | 
						|
    The default epoch is :rc:`dates.epoch` (by default 1970-01-01T00:00).
 | 
						|
 | 
						|
    If microsecond accuracy is desired, the date being plotted needs to be
 | 
						|
    within approximately 70 years of the epoch. Matplotlib internally
 | 
						|
    represents dates as days since the epoch, so floating point dynamic
 | 
						|
    range needs to be within a factor fo 2^52.
 | 
						|
 | 
						|
    `~.dates.set_epoch` must be called before any dates are converted
 | 
						|
    (i.e. near the import section) or a RuntimeError will be raised.
 | 
						|
 | 
						|
    See also :doc:`/gallery/ticks_and_spines/date_precision_and_epochs`.
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    epoch : str
 | 
						|
        valid UTC date parsable by `numpy.datetime64` (do not include
 | 
						|
        timezone).
 | 
						|
 | 
						|
    """
 | 
						|
    global _epoch
 | 
						|
    if _epoch is not None:
 | 
						|
        raise RuntimeError('set_epoch must be called before dates plotted.')
 | 
						|
    _epoch = epoch
 | 
						|
 | 
						|
 | 
						|
def get_epoch():
 | 
						|
    """
 | 
						|
    Get the epoch used by `.dates`.
 | 
						|
 | 
						|
    Returns
 | 
						|
    -------
 | 
						|
    epoch: str
 | 
						|
        String for the epoch (parsable by `numpy.datetime64`).
 | 
						|
    """
 | 
						|
    global _epoch
 | 
						|
 | 
						|
    if _epoch is None:
 | 
						|
        _epoch = matplotlib.rcParams['date.epoch']
 | 
						|
    return _epoch
 | 
						|
 | 
						|
 | 
						|
def _to_ordinalf(dt):
 | 
						|
    """
 | 
						|
    Convert :mod:`datetime` or :mod:`date` to the Gregorian date as UTC float
 | 
						|
    days, preserving hours, minutes, seconds and microseconds.  Return value
 | 
						|
    is a `float`.
 | 
						|
    """
 | 
						|
    # Convert to UTC
 | 
						|
    tzi = getattr(dt, 'tzinfo', None)
 | 
						|
    if tzi is not None:
 | 
						|
        dt = dt.astimezone(UTC)
 | 
						|
        dt = dt.replace(tzinfo=None)
 | 
						|
    dt64 = np.datetime64(dt)
 | 
						|
    return _dt64_to_ordinalf(dt64)
 | 
						|
 | 
						|
 | 
						|
# a version of _to_ordinalf that can operate on numpy arrays
 | 
						|
_to_ordinalf_np_vectorized = np.vectorize(_to_ordinalf)
 | 
						|
 | 
						|
 | 
						|
def _dt64_to_ordinalf(d):
 | 
						|
    """
 | 
						|
    Convert `numpy.datetime64` or an ndarray of those types to Gregorian
 | 
						|
    date as UTC float relative to the epoch (see `.get_epoch`).  Roundoff
 | 
						|
    is float64 precision.  Practically: microseconds for dates between
 | 
						|
    290301 BC, 294241 AD, milliseconds for larger dates
 | 
						|
    (see `numpy.datetime64`).
 | 
						|
    """
 | 
						|
 | 
						|
    # the "extra" ensures that we at least allow the dynamic range out to
 | 
						|
    # seconds.  That should get out to +/-2e11 years.
 | 
						|
    dseconds = d.astype('datetime64[s]')
 | 
						|
    extra = (d - dseconds).astype('timedelta64[ns]')
 | 
						|
    t0 = np.datetime64(get_epoch(), 's')
 | 
						|
    dt = (dseconds - t0).astype(np.float64)
 | 
						|
    dt += extra.astype(np.float64) / 1.0e9
 | 
						|
    dt = dt / SEC_PER_DAY
 | 
						|
 | 
						|
    NaT_int = np.datetime64('NaT').astype(np.int64)
 | 
						|
    d_int = d.astype(np.int64)
 | 
						|
    try:
 | 
						|
        dt[d_int == NaT_int] = np.nan
 | 
						|
    except TypeError:
 | 
						|
        if d_int == NaT_int:
 | 
						|
            dt = np.nan
 | 
						|
    return dt
 | 
						|
 | 
						|
 | 
						|
def _from_ordinalf(x, tz=None):
 | 
						|
    """
 | 
						|
    Convert Gregorian float of the date, preserving hours, minutes,
 | 
						|
    seconds and microseconds.  Return value is a `.datetime`.
 | 
						|
 | 
						|
    The input date *x* is a float in ordinal days at UTC, and the output will
 | 
						|
    be the specified `.datetime` object corresponding to that time in
 | 
						|
    timezone *tz*, or if *tz* is ``None``, in the timezone specified in
 | 
						|
    :rc:`timezone`.
 | 
						|
    """
 | 
						|
 | 
						|
    if tz is None:
 | 
						|
        tz = _get_rc_timezone()
 | 
						|
 | 
						|
    dt = (np.datetime64(get_epoch()) +
 | 
						|
          np.timedelta64(int(np.round(x * MUSECONDS_PER_DAY)), 'us'))
 | 
						|
    if dt < np.datetime64('0001-01-01') or dt >= np.datetime64('10000-01-01'):
 | 
						|
        raise ValueError(f'Date ordinal {x} converts to {dt} (using '
 | 
						|
                         f'epoch {get_epoch()}), but Matplotlib dates must be '
 | 
						|
                          'between year 0001 and 9999.')
 | 
						|
    # convert from datetime64 to datetime:
 | 
						|
    dt = dt.tolist()
 | 
						|
 | 
						|
    # datetime64 is always UTC:
 | 
						|
    dt = dt.replace(tzinfo=dateutil.tz.gettz('UTC'))
 | 
						|
    # but maybe we are working in a different timezone so move.
 | 
						|
    dt = dt.astimezone(tz)
 | 
						|
    # fix round off errors
 | 
						|
    if np.abs(x) > 70 * 365:
 | 
						|
        # if x is big, round off to nearest twenty microseconds.
 | 
						|
        # This avoids floating point roundoff error
 | 
						|
        ms = round(dt.microsecond / 20) * 20
 | 
						|
        if ms == 1000000:
 | 
						|
            dt = dt.replace(microsecond=0) + datetime.timedelta(seconds=1)
 | 
						|
        else:
 | 
						|
            dt = dt.replace(microsecond=ms)
 | 
						|
 | 
						|
    return dt
 | 
						|
 | 
						|
 | 
						|
# a version of _from_ordinalf that can operate on numpy arrays
 | 
						|
_from_ordinalf_np_vectorized = np.vectorize(_from_ordinalf, otypes="O")
 | 
						|
 | 
						|
 | 
						|
# a version of dateutil.parser.parse that can operate on numpy arrays
 | 
						|
_dateutil_parser_parse_np_vectorized = np.vectorize(dateutil.parser.parse)
 | 
						|
 | 
						|
 | 
						|
def datestr2num(d, default=None):
 | 
						|
    """
 | 
						|
    Convert a date string to a datenum using `dateutil.parser.parse`.
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    d : str or sequence of str
 | 
						|
        The dates to convert.
 | 
						|
 | 
						|
    default : datetime.datetime, optional
 | 
						|
        The default date to use when fields are missing in *d*.
 | 
						|
    """
 | 
						|
    if isinstance(d, str):
 | 
						|
        dt = dateutil.parser.parse(d, default=default)
 | 
						|
        return date2num(dt)
 | 
						|
    else:
 | 
						|
        if default is not None:
 | 
						|
            d = [dateutil.parser.parse(s, default=default) for s in d]
 | 
						|
        d = np.asarray(d)
 | 
						|
        if not d.size:
 | 
						|
            return d
 | 
						|
        return date2num(_dateutil_parser_parse_np_vectorized(d))
 | 
						|
 | 
						|
 | 
						|
def date2num(d):
 | 
						|
    """
 | 
						|
    Convert datetime objects to Matplotlib dates.
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    d : `datetime.datetime` or `numpy.datetime64` or sequences of these
 | 
						|
 | 
						|
    Returns
 | 
						|
    -------
 | 
						|
    float or sequence of floats
 | 
						|
        Number of days since the epoch.  See `.get_epoch` for the
 | 
						|
        epoch, which can be changed by :rc:`date.epoch` or `.set_epoch`.  If
 | 
						|
        the epoch is "1970-01-01T00:00:00" (default) then noon Jan 1 1970
 | 
						|
        ("1970-01-01T12:00:00") returns 0.5.
 | 
						|
 | 
						|
    Notes
 | 
						|
    -----
 | 
						|
    The Gregorian calendar is assumed; this is not universal practice.
 | 
						|
    For details see the module docstring.
 | 
						|
    """
 | 
						|
    if hasattr(d, "values"):
 | 
						|
        # this unpacks pandas series or dataframes...
 | 
						|
        d = d.values
 | 
						|
    if not np.iterable(d):
 | 
						|
        if (isinstance(d, np.datetime64) or
 | 
						|
                (isinstance(d, np.ndarray) and
 | 
						|
                 np.issubdtype(d.dtype, np.datetime64))):
 | 
						|
            return _dt64_to_ordinalf(d)
 | 
						|
        return _to_ordinalf(d)
 | 
						|
 | 
						|
    else:
 | 
						|
        d = np.asarray(d)
 | 
						|
        if np.issubdtype(d.dtype, np.datetime64):
 | 
						|
            return _dt64_to_ordinalf(d)
 | 
						|
        if not d.size:
 | 
						|
            return d
 | 
						|
        return _to_ordinalf_np_vectorized(d)
 | 
						|
 | 
						|
 | 
						|
def julian2num(j):
 | 
						|
    """
 | 
						|
    Convert a Julian date (or sequence) to a Matplotlib date (or sequence).
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    j : float or sequence of floats
 | 
						|
        Julian dates (days relative to 4713 BC Jan 1, 12:00:00 Julian
 | 
						|
        calendar or 4714 BC Nov 24, 12:00:00, proleptic Gregorian calendar).
 | 
						|
 | 
						|
    Returns
 | 
						|
    -------
 | 
						|
    float or sequence of floats
 | 
						|
        Matplotlib dates (days relative to `.get_epoch`).
 | 
						|
    """
 | 
						|
    ep = np.datetime64(get_epoch(), 'h').astype(float) / 24.
 | 
						|
    ep0 = np.datetime64('0000-12-31T00:00:00', 'h').astype(float) / 24.
 | 
						|
    # Julian offset defined above is relative to 0000-12-31, but we need
 | 
						|
    # relative to our current epoch:
 | 
						|
    dt = JULIAN_OFFSET - ep0 + ep
 | 
						|
    return np.subtract(j, dt)  # Handles both scalar & nonscalar j.
 | 
						|
 | 
						|
 | 
						|
def num2julian(n):
 | 
						|
    """
 | 
						|
    Convert a Matplotlib date (or sequence) to a Julian date (or sequence).
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    n : float or sequence of floats
 | 
						|
        Matplotlib dates (days relative to `.get_epoch`).
 | 
						|
 | 
						|
    Returns
 | 
						|
    -------
 | 
						|
    float or sequence of floats
 | 
						|
        Julian dates (days relative to 4713 BC Jan 1, 12:00:00).
 | 
						|
    """
 | 
						|
    ep = np.datetime64(get_epoch(), 'h').astype(float) / 24.
 | 
						|
    ep0 = np.datetime64('0000-12-31T00:00:00', 'h').astype(float) / 24.
 | 
						|
    # Julian offset defined above is relative to 0000-12-31, but we need
 | 
						|
    # relative to our current epoch:
 | 
						|
    dt = JULIAN_OFFSET - ep0 + ep
 | 
						|
    return np.add(n, dt)  # Handles both scalar & nonscalar j.
 | 
						|
 | 
						|
 | 
						|
def num2date(x, tz=None):
 | 
						|
    """
 | 
						|
    Convert Matplotlib dates to `~datetime.datetime` objects.
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    x : float or sequence of floats
 | 
						|
        Number of days (fraction part represents hours, minutes, seconds)
 | 
						|
        since the epoch.  See `.get_epoch` for the
 | 
						|
        epoch, which can be changed by :rc:`date.epoch` or `.set_epoch`.
 | 
						|
    tz : str, optional
 | 
						|
        Timezone of *x* (defaults to :rc:`timezone`).
 | 
						|
 | 
						|
    Returns
 | 
						|
    -------
 | 
						|
    `~datetime.datetime` or sequence of `~datetime.datetime`
 | 
						|
        Dates are returned in timezone *tz*.
 | 
						|
 | 
						|
        If *x* is a sequence, a sequence of `~datetime.datetime` objects will
 | 
						|
        be returned.
 | 
						|
 | 
						|
    Notes
 | 
						|
    -----
 | 
						|
    The addition of one here is a historical artifact. Also, note that the
 | 
						|
    Gregorian calendar is assumed; this is not universal practice.
 | 
						|
    For details, see the module docstring.
 | 
						|
    """
 | 
						|
    if tz is None:
 | 
						|
        tz = _get_rc_timezone()
 | 
						|
    return _from_ordinalf_np_vectorized(x, tz).tolist()
 | 
						|
 | 
						|
 | 
						|
_ordinalf_to_timedelta_np_vectorized = np.vectorize(
 | 
						|
    lambda x: datetime.timedelta(days=x), otypes="O")
 | 
						|
 | 
						|
 | 
						|
def num2timedelta(x):
 | 
						|
    """
 | 
						|
    Convert number of days to a `~datetime.timedelta` object.
 | 
						|
 | 
						|
    If *x* is a sequence, a sequence of `~datetime.timedelta` objects will
 | 
						|
    be returned.
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    x : float, sequence of floats
 | 
						|
        Number of days. The fraction part represents hours, minutes, seconds.
 | 
						|
 | 
						|
    Returns
 | 
						|
    -------
 | 
						|
    `datetime.timedelta` or list[`datetime.timedelta`]
 | 
						|
    """
 | 
						|
    return _ordinalf_to_timedelta_np_vectorized(x).tolist()
 | 
						|
 | 
						|
 | 
						|
def drange(dstart, dend, delta):
 | 
						|
    """
 | 
						|
    Return a sequence of equally spaced Matplotlib dates.
 | 
						|
 | 
						|
    The dates start at *dstart* and reach up to, but not including *dend*.
 | 
						|
    They are spaced by *delta*.
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    dstart, dend : `~datetime.datetime`
 | 
						|
        The date limits.
 | 
						|
    delta : `datetime.timedelta`
 | 
						|
        Spacing of the dates.
 | 
						|
 | 
						|
    Returns
 | 
						|
    -------
 | 
						|
    `numpy.array`
 | 
						|
        A list floats representing Matplotlib dates.
 | 
						|
 | 
						|
    """
 | 
						|
    f1 = date2num(dstart)
 | 
						|
    f2 = date2num(dend)
 | 
						|
    step = delta.total_seconds() / SEC_PER_DAY
 | 
						|
 | 
						|
    # calculate the difference between dend and dstart in times of delta
 | 
						|
    num = int(np.ceil((f2 - f1) / step))
 | 
						|
 | 
						|
    # calculate end of the interval which will be generated
 | 
						|
    dinterval_end = dstart + num * delta
 | 
						|
 | 
						|
    # ensure, that an half open interval will be generated [dstart, dend)
 | 
						|
    if dinterval_end >= dend:
 | 
						|
        # if the endpoint is greater than dend, just subtract one delta
 | 
						|
        dinterval_end -= delta
 | 
						|
        num -= 1
 | 
						|
 | 
						|
    f2 = date2num(dinterval_end)  # new float-endpoint
 | 
						|
    return np.linspace(f1, f2, num + 1)
 | 
						|
 | 
						|
## date tickers and formatters ###
 | 
						|
 | 
						|
 | 
						|
class DateFormatter(ticker.Formatter):
 | 
						|
    """
 | 
						|
    Format a tick (in days since the epoch) with a
 | 
						|
    `~datetime.datetime.strftime` format string.
 | 
						|
    """
 | 
						|
 | 
						|
    @cbook.deprecated("3.3")
 | 
						|
    @property
 | 
						|
    def illegal_s(self):
 | 
						|
        return re.compile(r"((^|[^%])(%%)*%s)")
 | 
						|
 | 
						|
    def __init__(self, fmt, tz=None):
 | 
						|
        """
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        fmt : str
 | 
						|
            `~datetime.datetime.strftime` format string
 | 
						|
        tz : `datetime.tzinfo`, default: :rc:`timezone`
 | 
						|
            Ticks timezone.
 | 
						|
        """
 | 
						|
        if tz is None:
 | 
						|
            tz = _get_rc_timezone()
 | 
						|
        self.fmt = fmt
 | 
						|
        self.tz = tz
 | 
						|
 | 
						|
    def __call__(self, x, pos=0):
 | 
						|
        return num2date(x, self.tz).strftime(self.fmt)
 | 
						|
 | 
						|
    def set_tzinfo(self, tz):
 | 
						|
        self.tz = tz
 | 
						|
 | 
						|
 | 
						|
@cbook.deprecated("3.3")
 | 
						|
class IndexDateFormatter(ticker.Formatter):
 | 
						|
    """Use with `.IndexLocator` to cycle format strings by index."""
 | 
						|
 | 
						|
    def __init__(self, t, fmt, tz=None):
 | 
						|
        """
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        t : list of float
 | 
						|
            A sequence of dates (floating point days).
 | 
						|
        fmt : str
 | 
						|
            A `~datetime.datetime.strftime` format string.
 | 
						|
        """
 | 
						|
        if tz is None:
 | 
						|
            tz = _get_rc_timezone()
 | 
						|
        self.t = t
 | 
						|
        self.fmt = fmt
 | 
						|
        self.tz = tz
 | 
						|
 | 
						|
    def __call__(self, x, pos=0):
 | 
						|
        """Return the label for time *x* at position *pos*."""
 | 
						|
        ind = int(round(x))
 | 
						|
        if ind >= len(self.t) or ind <= 0:
 | 
						|
            return ''
 | 
						|
        return num2date(self.t[ind], self.tz).strftime(self.fmt)
 | 
						|
 | 
						|
 | 
						|
class ConciseDateFormatter(ticker.Formatter):
 | 
						|
    """
 | 
						|
    A `.Formatter` which attempts to figure out the best format to use for the
 | 
						|
    date, and to make it as compact as possible, but still be complete. This is
 | 
						|
    most useful when used with the `AutoDateLocator`::
 | 
						|
 | 
						|
    >>> locator = AutoDateLocator()
 | 
						|
    >>> formatter = ConciseDateFormatter(locator)
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    locator : `.ticker.Locator`
 | 
						|
        Locator that this axis is using.
 | 
						|
 | 
						|
    tz : str, optional
 | 
						|
        Passed to `.dates.date2num`.
 | 
						|
 | 
						|
    formats : list of 6 strings, optional
 | 
						|
        Format strings for 6 levels of tick labelling: mostly years,
 | 
						|
        months, days, hours, minutes, and seconds.  Strings use
 | 
						|
        the same format codes as `~datetime.datetime.strftime`.  Default is
 | 
						|
        ``['%Y', '%b', '%d', '%H:%M', '%H:%M', '%S.%f']``
 | 
						|
 | 
						|
    zero_formats : list of 6 strings, optional
 | 
						|
        Format strings for tick labels that are "zeros" for a given tick
 | 
						|
        level.  For instance, if most ticks are months, ticks around 1 Jan 2005
 | 
						|
        will be labeled "Dec", "2005", "Feb".  The default is
 | 
						|
        ``['', '%Y', '%b', '%b-%d', '%H:%M', '%H:%M']``
 | 
						|
 | 
						|
    offset_formats : list of 6 strings, optional
 | 
						|
        Format strings for the 6 levels that is applied to the "offset"
 | 
						|
        string found on the right side of an x-axis, or top of a y-axis.
 | 
						|
        Combined with the tick labels this should completely specify the
 | 
						|
        date.  The default is::
 | 
						|
 | 
						|
            ['', '%Y', '%Y-%b', '%Y-%b-%d', '%Y-%b-%d', '%Y-%b-%d %H:%M']
 | 
						|
 | 
						|
    show_offset : bool, default: True
 | 
						|
        Whether to show the offset or not.
 | 
						|
 | 
						|
    Examples
 | 
						|
    --------
 | 
						|
    See :doc:`/gallery/ticks_and_spines/date_concise_formatter`
 | 
						|
 | 
						|
    .. plot::
 | 
						|
 | 
						|
        import datetime
 | 
						|
        import matplotlib.dates as mdates
 | 
						|
 | 
						|
        base = datetime.datetime(2005, 2, 1)
 | 
						|
        dates = np.array([base + datetime.timedelta(hours=(2 * i))
 | 
						|
                          for i in range(732)])
 | 
						|
        N = len(dates)
 | 
						|
        np.random.seed(19680801)
 | 
						|
        y = np.cumsum(np.random.randn(N))
 | 
						|
 | 
						|
        fig, ax = plt.subplots(constrained_layout=True)
 | 
						|
        locator = mdates.AutoDateLocator()
 | 
						|
        formatter = mdates.ConciseDateFormatter(locator)
 | 
						|
        ax.xaxis.set_major_locator(locator)
 | 
						|
        ax.xaxis.set_major_formatter(formatter)
 | 
						|
 | 
						|
        ax.plot(dates, y)
 | 
						|
        ax.set_title('Concise Date Formatter')
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, locator, tz=None, formats=None, offset_formats=None,
 | 
						|
                 zero_formats=None, show_offset=True):
 | 
						|
        """
 | 
						|
        Autoformat the date labels.  The default format is used to form an
 | 
						|
        initial string, and then redundant elements are removed.
 | 
						|
        """
 | 
						|
        self._locator = locator
 | 
						|
        self._tz = tz
 | 
						|
        self.defaultfmt = '%Y'
 | 
						|
        # there are 6 levels with each level getting a specific format
 | 
						|
        # 0: mostly years,  1: months,  2: days,
 | 
						|
        # 3: hours, 4: minutes, 5: seconds
 | 
						|
        if formats:
 | 
						|
            if len(formats) != 6:
 | 
						|
                raise ValueError('formats argument must be a list of '
 | 
						|
                                 '6 format strings (or None)')
 | 
						|
            self.formats = formats
 | 
						|
        else:
 | 
						|
            self.formats = ['%Y',  # ticks are mostly years
 | 
						|
                            '%b',          # ticks are mostly months
 | 
						|
                            '%d',          # ticks are mostly days
 | 
						|
                            '%H:%M',       # hrs
 | 
						|
                            '%H:%M',       # min
 | 
						|
                            '%S.%f',       # secs
 | 
						|
                            ]
 | 
						|
        # fmt for zeros ticks at this level.  These are
 | 
						|
        # ticks that should be labeled w/ info the level above.
 | 
						|
        # like 1 Jan can just be labelled "Jan".  02:02:00 can
 | 
						|
        # just be labeled 02:02.
 | 
						|
        if zero_formats:
 | 
						|
            if len(zero_formats) != 6:
 | 
						|
                raise ValueError('zero_formats argument must be a list of '
 | 
						|
                                 '6 format strings (or None)')
 | 
						|
            self.zero_formats = zero_formats
 | 
						|
        elif formats:
 | 
						|
            # use the users formats for the zero tick formats
 | 
						|
            self.zero_formats = [''] + self.formats[:-1]
 | 
						|
        else:
 | 
						|
            # make the defaults a bit nicer:
 | 
						|
            self.zero_formats = [''] + self.formats[:-1]
 | 
						|
            self.zero_formats[3] = '%b-%d'
 | 
						|
 | 
						|
        if offset_formats:
 | 
						|
            if len(offset_formats) != 6:
 | 
						|
                raise ValueError('offsetfmts argument must be a list of '
 | 
						|
                                 '6 format strings (or None)')
 | 
						|
            self.offset_formats = offset_formats
 | 
						|
        else:
 | 
						|
            self.offset_formats = ['',
 | 
						|
                                   '%Y',
 | 
						|
                                   '%Y-%b',
 | 
						|
                                   '%Y-%b-%d',
 | 
						|
                                   '%Y-%b-%d',
 | 
						|
                                   '%Y-%b-%d %H:%M']
 | 
						|
        self.offset_string = ''
 | 
						|
        self.show_offset = show_offset
 | 
						|
 | 
						|
    def __call__(self, x, pos=None):
 | 
						|
        formatter = DateFormatter(self.defaultfmt, self._tz)
 | 
						|
        return formatter(x, pos=pos)
 | 
						|
 | 
						|
    def format_ticks(self, values):
 | 
						|
        tickdatetime = [num2date(value, tz=self._tz) for value in values]
 | 
						|
        tickdate = np.array([tdt.timetuple()[:6] for tdt in tickdatetime])
 | 
						|
 | 
						|
        # basic algorithm:
 | 
						|
        # 1) only display a part of the date if it changes over the ticks.
 | 
						|
        # 2) don't display the smaller part of the date if:
 | 
						|
        #    it is always the same or if it is the start of the
 | 
						|
        #    year, month, day etc.
 | 
						|
        # fmt for most ticks at this level
 | 
						|
        fmts = self.formats
 | 
						|
        # format beginnings of days, months, years, etc...
 | 
						|
        zerofmts = self.zero_formats
 | 
						|
        # offset fmt are for the offset in the upper left of the
 | 
						|
        # or lower right of the axis.
 | 
						|
        offsetfmts = self.offset_formats
 | 
						|
 | 
						|
        # determine the level we will label at:
 | 
						|
        # mostly 0: years,  1: months,  2: days,
 | 
						|
        # 3: hours, 4: minutes, 5: seconds, 6: microseconds
 | 
						|
        for level in range(5, -1, -1):
 | 
						|
            if len(np.unique(tickdate[:, level])) > 1:
 | 
						|
                break
 | 
						|
 | 
						|
        # level is the basic level we will label at.
 | 
						|
        # now loop through and decide the actual ticklabels
 | 
						|
        zerovals = [0, 1, 1, 0, 0, 0, 0]
 | 
						|
        labels = [''] * len(tickdate)
 | 
						|
        for nn in range(len(tickdate)):
 | 
						|
            if level < 5:
 | 
						|
                if tickdate[nn][level] == zerovals[level]:
 | 
						|
                    fmt = zerofmts[level]
 | 
						|
                else:
 | 
						|
                    fmt = fmts[level]
 | 
						|
            else:
 | 
						|
                # special handling for seconds + microseconds
 | 
						|
                if (tickdatetime[nn].second == tickdatetime[nn].microsecond
 | 
						|
                        == 0):
 | 
						|
                    fmt = zerofmts[level]
 | 
						|
                else:
 | 
						|
                    fmt = fmts[level]
 | 
						|
            labels[nn] = tickdatetime[nn].strftime(fmt)
 | 
						|
 | 
						|
        # special handling of seconds and microseconds:
 | 
						|
        # strip extra zeros and decimal if possible.
 | 
						|
        # this is complicated by two factors.  1) we have some level-4 strings
 | 
						|
        # here (i.e. 03:00, '0.50000', '1.000') 2) we would like to have the
 | 
						|
        # same number of decimals for each string (i.e. 0.5 and 1.0).
 | 
						|
        if level >= 5:
 | 
						|
            trailing_zeros = min(
 | 
						|
                (len(s) - len(s.rstrip('0')) for s in labels if '.' in s),
 | 
						|
                default=None)
 | 
						|
            if trailing_zeros:
 | 
						|
                for nn in range(len(labels)):
 | 
						|
                    if '.' in labels[nn]:
 | 
						|
                        labels[nn] = labels[nn][:-trailing_zeros].rstrip('.')
 | 
						|
 | 
						|
        if self.show_offset:
 | 
						|
            # set the offset string:
 | 
						|
            self.offset_string = tickdatetime[-1].strftime(offsetfmts[level])
 | 
						|
 | 
						|
        return labels
 | 
						|
 | 
						|
    def get_offset(self):
 | 
						|
        return self.offset_string
 | 
						|
 | 
						|
    def format_data_short(self, value):
 | 
						|
        return num2date(value, tz=self._tz).strftime('%Y-%m-%d %H:%M:%S')
 | 
						|
 | 
						|
 | 
						|
class AutoDateFormatter(ticker.Formatter):
 | 
						|
    """
 | 
						|
    A `.Formatter` which attempts to figure out the best format to use.  This
 | 
						|
    is most useful when used with the `AutoDateLocator`.
 | 
						|
 | 
						|
    The AutoDateFormatter has a scale dictionary that maps the scale
 | 
						|
    of the tick (the distance in days between one major tick) and a
 | 
						|
    format string.  The default looks like this::
 | 
						|
 | 
						|
        self.scaled = {
 | 
						|
            DAYS_PER_YEAR: rcParams['date.autoformat.year'],
 | 
						|
            DAYS_PER_MONTH: rcParams['date.autoformat.month'],
 | 
						|
            1.0: rcParams['date.autoformat.day'],
 | 
						|
            1. / HOURS_PER_DAY: rcParams['date.autoformat.hour'],
 | 
						|
            1. / (MINUTES_PER_DAY): rcParams['date.autoformat.minute'],
 | 
						|
            1. / (SEC_PER_DAY): rcParams['date.autoformat.second'],
 | 
						|
            1. / (MUSECONDS_PER_DAY): rcParams['date.autoformat.microsecond'],
 | 
						|
        }
 | 
						|
 | 
						|
    The algorithm picks the key in the dictionary that is >= the
 | 
						|
    current scale and uses that format string.  You can customize this
 | 
						|
    dictionary by doing::
 | 
						|
 | 
						|
    >>> locator = AutoDateLocator()
 | 
						|
    >>> formatter = AutoDateFormatter(locator)
 | 
						|
    >>> formatter.scaled[1/(24.*60.)] = '%M:%S' # only show min and sec
 | 
						|
 | 
						|
    A custom `.FuncFormatter` can also be used.  The following example shows
 | 
						|
    how to use a custom format function to strip trailing zeros from decimal
 | 
						|
    seconds and adds the date to the first ticklabel::
 | 
						|
 | 
						|
        >>> def my_format_function(x, pos=None):
 | 
						|
        ...     x = matplotlib.dates.num2date(x)
 | 
						|
        ...     if pos == 0:
 | 
						|
        ...         fmt = '%D %H:%M:%S.%f'
 | 
						|
        ...     else:
 | 
						|
        ...         fmt = '%H:%M:%S.%f'
 | 
						|
        ...     label = x.strftime(fmt)
 | 
						|
        ...     label = label.rstrip("0")
 | 
						|
        ...     label = label.rstrip(".")
 | 
						|
        ...     return label
 | 
						|
        >>> from matplotlib.ticker import FuncFormatter
 | 
						|
        >>> formatter.scaled[1/(24.*60.)] = FuncFormatter(my_format_function)
 | 
						|
    """
 | 
						|
 | 
						|
    # This can be improved by providing some user-level direction on
 | 
						|
    # how to choose the best format (precedence, etc...)
 | 
						|
 | 
						|
    # Perhaps a 'struct' that has a field for each time-type where a
 | 
						|
    # zero would indicate "don't show" and a number would indicate
 | 
						|
    # "show" with some sort of priority.  Same priorities could mean
 | 
						|
    # show all with the same priority.
 | 
						|
 | 
						|
    # Or more simply, perhaps just a format string for each
 | 
						|
    # possibility...
 | 
						|
 | 
						|
    def __init__(self, locator, tz=None, defaultfmt='%Y-%m-%d'):
 | 
						|
        """
 | 
						|
        Autoformat the date labels.  The default format is the one to use
 | 
						|
        if none of the values in ``self.scaled`` are greater than the unit
 | 
						|
        returned by ``locator._get_unit()``.
 | 
						|
        """
 | 
						|
        self._locator = locator
 | 
						|
        self._tz = tz
 | 
						|
        self.defaultfmt = defaultfmt
 | 
						|
        self._formatter = DateFormatter(self.defaultfmt, tz)
 | 
						|
        rcParams = matplotlib.rcParams
 | 
						|
        self.scaled = {
 | 
						|
            DAYS_PER_YEAR: rcParams['date.autoformatter.year'],
 | 
						|
            DAYS_PER_MONTH: rcParams['date.autoformatter.month'],
 | 
						|
            1: rcParams['date.autoformatter.day'],
 | 
						|
            1 / HOURS_PER_DAY: rcParams['date.autoformatter.hour'],
 | 
						|
            1 / MINUTES_PER_DAY: rcParams['date.autoformatter.minute'],
 | 
						|
            1 / SEC_PER_DAY: rcParams['date.autoformatter.second'],
 | 
						|
            1 / MUSECONDS_PER_DAY: rcParams['date.autoformatter.microsecond']
 | 
						|
        }
 | 
						|
 | 
						|
    def _set_locator(self, locator):
 | 
						|
        self._locator = locator
 | 
						|
 | 
						|
    def __call__(self, x, pos=None):
 | 
						|
        try:
 | 
						|
            locator_unit_scale = float(self._locator._get_unit())
 | 
						|
        except AttributeError:
 | 
						|
            locator_unit_scale = 1
 | 
						|
        # Pick the first scale which is greater than the locator unit.
 | 
						|
        fmt = next((fmt for scale, fmt in sorted(self.scaled.items())
 | 
						|
                    if scale >= locator_unit_scale),
 | 
						|
                   self.defaultfmt)
 | 
						|
 | 
						|
        if isinstance(fmt, str):
 | 
						|
            self._formatter = DateFormatter(fmt, self._tz)
 | 
						|
            result = self._formatter(x, pos)
 | 
						|
        elif callable(fmt):
 | 
						|
            result = fmt(x, pos)
 | 
						|
        else:
 | 
						|
            raise TypeError('Unexpected type passed to {0!r}.'.format(self))
 | 
						|
 | 
						|
        return result
 | 
						|
 | 
						|
 | 
						|
class rrulewrapper:
 | 
						|
    def __init__(self, freq, tzinfo=None, **kwargs):
 | 
						|
        kwargs['freq'] = freq
 | 
						|
        self._base_tzinfo = tzinfo
 | 
						|
 | 
						|
        self._update_rrule(**kwargs)
 | 
						|
 | 
						|
    def set(self, **kwargs):
 | 
						|
        self._construct.update(kwargs)
 | 
						|
 | 
						|
        self._update_rrule(**self._construct)
 | 
						|
 | 
						|
    def _update_rrule(self, **kwargs):
 | 
						|
        tzinfo = self._base_tzinfo
 | 
						|
 | 
						|
        # rrule does not play nicely with time zones - especially pytz time
 | 
						|
        # zones, it's best to use naive zones and attach timezones once the
 | 
						|
        # datetimes are returned
 | 
						|
        if 'dtstart' in kwargs:
 | 
						|
            dtstart = kwargs['dtstart']
 | 
						|
            if dtstart.tzinfo is not None:
 | 
						|
                if tzinfo is None:
 | 
						|
                    tzinfo = dtstart.tzinfo
 | 
						|
                else:
 | 
						|
                    dtstart = dtstart.astimezone(tzinfo)
 | 
						|
 | 
						|
                kwargs['dtstart'] = dtstart.replace(tzinfo=None)
 | 
						|
 | 
						|
        if 'until' in kwargs:
 | 
						|
            until = kwargs['until']
 | 
						|
            if until.tzinfo is not None:
 | 
						|
                if tzinfo is not None:
 | 
						|
                    until = until.astimezone(tzinfo)
 | 
						|
                else:
 | 
						|
                    raise ValueError('until cannot be aware if dtstart '
 | 
						|
                                     'is naive and tzinfo is None')
 | 
						|
 | 
						|
                kwargs['until'] = until.replace(tzinfo=None)
 | 
						|
 | 
						|
        self._construct = kwargs.copy()
 | 
						|
        self._tzinfo = tzinfo
 | 
						|
        self._rrule = rrule(**self._construct)
 | 
						|
 | 
						|
    def _attach_tzinfo(self, dt, tzinfo):
 | 
						|
        # pytz zones are attached by "localizing" the datetime
 | 
						|
        if hasattr(tzinfo, 'localize'):
 | 
						|
            return tzinfo.localize(dt, is_dst=True)
 | 
						|
 | 
						|
        return dt.replace(tzinfo=tzinfo)
 | 
						|
 | 
						|
    def _aware_return_wrapper(self, f, returns_list=False):
 | 
						|
        """Decorator function that allows rrule methods to handle tzinfo."""
 | 
						|
        # This is only necessary if we're actually attaching a tzinfo
 | 
						|
        if self._tzinfo is None:
 | 
						|
            return f
 | 
						|
 | 
						|
        # All datetime arguments must be naive. If they are not naive, they are
 | 
						|
        # converted to the _tzinfo zone before dropping the zone.
 | 
						|
        def normalize_arg(arg):
 | 
						|
            if isinstance(arg, datetime.datetime) and arg.tzinfo is not None:
 | 
						|
                if arg.tzinfo is not self._tzinfo:
 | 
						|
                    arg = arg.astimezone(self._tzinfo)
 | 
						|
 | 
						|
                return arg.replace(tzinfo=None)
 | 
						|
 | 
						|
            return arg
 | 
						|
 | 
						|
        def normalize_args(args, kwargs):
 | 
						|
            args = tuple(normalize_arg(arg) for arg in args)
 | 
						|
            kwargs = {kw: normalize_arg(arg) for kw, arg in kwargs.items()}
 | 
						|
 | 
						|
            return args, kwargs
 | 
						|
 | 
						|
        # There are two kinds of functions we care about - ones that return
 | 
						|
        # dates and ones that return lists of dates.
 | 
						|
        if not returns_list:
 | 
						|
            def inner_func(*args, **kwargs):
 | 
						|
                args, kwargs = normalize_args(args, kwargs)
 | 
						|
                dt = f(*args, **kwargs)
 | 
						|
                return self._attach_tzinfo(dt, self._tzinfo)
 | 
						|
        else:
 | 
						|
            def inner_func(*args, **kwargs):
 | 
						|
                args, kwargs = normalize_args(args, kwargs)
 | 
						|
                dts = f(*args, **kwargs)
 | 
						|
                return [self._attach_tzinfo(dt, self._tzinfo) for dt in dts]
 | 
						|
 | 
						|
        return functools.wraps(f)(inner_func)
 | 
						|
 | 
						|
    def __getattr__(self, name):
 | 
						|
        if name in self.__dict__:
 | 
						|
            return self.__dict__[name]
 | 
						|
 | 
						|
        f = getattr(self._rrule, name)
 | 
						|
 | 
						|
        if name in {'after', 'before'}:
 | 
						|
            return self._aware_return_wrapper(f)
 | 
						|
        elif name in {'xafter', 'xbefore', 'between'}:
 | 
						|
            return self._aware_return_wrapper(f, returns_list=True)
 | 
						|
        else:
 | 
						|
            return f
 | 
						|
 | 
						|
    def __setstate__(self, state):
 | 
						|
        self.__dict__.update(state)
 | 
						|
 | 
						|
 | 
						|
class DateLocator(ticker.Locator):
 | 
						|
    """
 | 
						|
    Determines the tick locations when plotting dates.
 | 
						|
 | 
						|
    This class is subclassed by other Locators and
 | 
						|
    is not meant to be used on its own.
 | 
						|
    """
 | 
						|
    hms0d = {'byhour': 0, 'byminute': 0, 'bysecond': 0}
 | 
						|
 | 
						|
    def __init__(self, tz=None):
 | 
						|
        """
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        tz : `datetime.tzinfo`
 | 
						|
        """
 | 
						|
        if tz is None:
 | 
						|
            tz = _get_rc_timezone()
 | 
						|
        self.tz = tz
 | 
						|
 | 
						|
    def set_tzinfo(self, tz):
 | 
						|
        """
 | 
						|
        Set time zone info.
 | 
						|
        """
 | 
						|
        self.tz = tz
 | 
						|
 | 
						|
    def datalim_to_dt(self):
 | 
						|
        """Convert axis data interval to datetime objects."""
 | 
						|
        dmin, dmax = self.axis.get_data_interval()
 | 
						|
        if dmin > dmax:
 | 
						|
            dmin, dmax = dmax, dmin
 | 
						|
 | 
						|
        return num2date(dmin, self.tz), num2date(dmax, self.tz)
 | 
						|
 | 
						|
    def viewlim_to_dt(self):
 | 
						|
        """Convert the view interval to datetime objects."""
 | 
						|
        vmin, vmax = self.axis.get_view_interval()
 | 
						|
        if vmin > vmax:
 | 
						|
            vmin, vmax = vmax, vmin
 | 
						|
        return num2date(vmin, self.tz), num2date(vmax, self.tz)
 | 
						|
 | 
						|
    def _get_unit(self):
 | 
						|
        """
 | 
						|
        Return how many days a unit of the locator is; used for
 | 
						|
        intelligent autoscaling.
 | 
						|
        """
 | 
						|
        return 1
 | 
						|
 | 
						|
    def _get_interval(self):
 | 
						|
        """
 | 
						|
        Return the number of units for each tick.
 | 
						|
        """
 | 
						|
        return 1
 | 
						|
 | 
						|
    def nonsingular(self, vmin, vmax):
 | 
						|
        """
 | 
						|
        Given the proposed upper and lower extent, adjust the range
 | 
						|
        if it is too close to being singular (i.e. a range of ~0).
 | 
						|
        """
 | 
						|
        if not np.isfinite(vmin) or not np.isfinite(vmax):
 | 
						|
            # Except if there is no data, then use 2000-2010 as default.
 | 
						|
            return (date2num(datetime.date(2000, 1, 1)),
 | 
						|
                    date2num(datetime.date(2010, 1, 1)))
 | 
						|
        if vmax < vmin:
 | 
						|
            vmin, vmax = vmax, vmin
 | 
						|
        unit = self._get_unit()
 | 
						|
        interval = self._get_interval()
 | 
						|
        if abs(vmax - vmin) < 1e-6:
 | 
						|
            vmin -= 2 * unit * interval
 | 
						|
            vmax += 2 * unit * interval
 | 
						|
        return vmin, vmax
 | 
						|
 | 
						|
 | 
						|
class RRuleLocator(DateLocator):
 | 
						|
    # use the dateutil rrule instance
 | 
						|
 | 
						|
    def __init__(self, o, tz=None):
 | 
						|
        DateLocator.__init__(self, tz)
 | 
						|
        self.rule = o
 | 
						|
 | 
						|
    def __call__(self):
 | 
						|
        # if no data have been set, this will tank with a ValueError
 | 
						|
        try:
 | 
						|
            dmin, dmax = self.viewlim_to_dt()
 | 
						|
        except ValueError:
 | 
						|
            return []
 | 
						|
 | 
						|
        return self.tick_values(dmin, dmax)
 | 
						|
 | 
						|
    def tick_values(self, vmin, vmax):
 | 
						|
        delta = relativedelta(vmax, vmin)
 | 
						|
 | 
						|
        # We need to cap at the endpoints of valid datetime
 | 
						|
        try:
 | 
						|
            start = vmin - delta
 | 
						|
        except (ValueError, OverflowError):
 | 
						|
            # cap
 | 
						|
            start = datetime.datetime(1, 1, 1, 0, 0, 0,
 | 
						|
                                      tzinfo=datetime.timezone.utc)
 | 
						|
 | 
						|
        try:
 | 
						|
            stop = vmax + delta
 | 
						|
        except (ValueError, OverflowError):
 | 
						|
            # cap
 | 
						|
            stop = datetime.datetime(9999, 12, 31, 23, 59, 59,
 | 
						|
                                     tzinfo=datetime.timezone.utc)
 | 
						|
 | 
						|
        self.rule.set(dtstart=start, until=stop)
 | 
						|
 | 
						|
        dates = self.rule.between(vmin, vmax, True)
 | 
						|
        if len(dates) == 0:
 | 
						|
            return date2num([vmin, vmax])
 | 
						|
        return self.raise_if_exceeds(date2num(dates))
 | 
						|
 | 
						|
    def _get_unit(self):
 | 
						|
        # docstring inherited
 | 
						|
        freq = self.rule._rrule._freq
 | 
						|
        return self.get_unit_generic(freq)
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def get_unit_generic(freq):
 | 
						|
        if freq == YEARLY:
 | 
						|
            return DAYS_PER_YEAR
 | 
						|
        elif freq == MONTHLY:
 | 
						|
            return DAYS_PER_MONTH
 | 
						|
        elif freq == WEEKLY:
 | 
						|
            return DAYS_PER_WEEK
 | 
						|
        elif freq == DAILY:
 | 
						|
            return 1.0
 | 
						|
        elif freq == HOURLY:
 | 
						|
            return 1.0 / HOURS_PER_DAY
 | 
						|
        elif freq == MINUTELY:
 | 
						|
            return 1.0 / MINUTES_PER_DAY
 | 
						|
        elif freq == SECONDLY:
 | 
						|
            return 1.0 / SEC_PER_DAY
 | 
						|
        else:
 | 
						|
            # error
 | 
						|
            return -1   # or should this just return '1'?
 | 
						|
 | 
						|
    def _get_interval(self):
 | 
						|
        return self.rule._rrule._interval
 | 
						|
 | 
						|
    @cbook.deprecated("3.2")
 | 
						|
    def autoscale(self):
 | 
						|
        """
 | 
						|
        Set the view limits to include the data range.
 | 
						|
        """
 | 
						|
        dmin, dmax = self.datalim_to_dt()
 | 
						|
        delta = relativedelta(dmax, dmin)
 | 
						|
 | 
						|
        # We need to cap at the endpoints of valid datetime
 | 
						|
        try:
 | 
						|
            start = dmin - delta
 | 
						|
        except ValueError:
 | 
						|
            start = _from_ordinalf(1.0)
 | 
						|
 | 
						|
        try:
 | 
						|
            stop = dmax + delta
 | 
						|
        except ValueError:
 | 
						|
            # The magic number!
 | 
						|
            stop = _from_ordinalf(3652059.9999999)
 | 
						|
 | 
						|
        self.rule.set(dtstart=start, until=stop)
 | 
						|
        dmin, dmax = self.datalim_to_dt()
 | 
						|
 | 
						|
        vmin = self.rule.before(dmin, True)
 | 
						|
        if not vmin:
 | 
						|
            vmin = dmin
 | 
						|
 | 
						|
        vmax = self.rule.after(dmax, True)
 | 
						|
        if not vmax:
 | 
						|
            vmax = dmax
 | 
						|
 | 
						|
        vmin = date2num(vmin)
 | 
						|
        vmax = date2num(vmax)
 | 
						|
 | 
						|
        return self.nonsingular(vmin, vmax)
 | 
						|
 | 
						|
 | 
						|
class AutoDateLocator(DateLocator):
 | 
						|
    """
 | 
						|
    On autoscale, this class picks the best `DateLocator` to set the view
 | 
						|
    limits and the tick locations.
 | 
						|
 | 
						|
    Attributes
 | 
						|
    ----------
 | 
						|
    intervald : dict
 | 
						|
 | 
						|
        Mapping of tick frequencies to multiples allowed for that ticking.
 | 
						|
        The default is ::
 | 
						|
 | 
						|
            self.intervald = {
 | 
						|
                YEARLY  : [1, 2, 4, 5, 10, 20, 40, 50, 100, 200, 400, 500,
 | 
						|
                           1000, 2000, 4000, 5000, 10000],
 | 
						|
                MONTHLY : [1, 2, 3, 4, 6],
 | 
						|
                DAILY   : [1, 2, 3, 7, 14, 21],
 | 
						|
                HOURLY  : [1, 2, 3, 4, 6, 12],
 | 
						|
                MINUTELY: [1, 5, 10, 15, 30],
 | 
						|
                SECONDLY: [1, 5, 10, 15, 30],
 | 
						|
                MICROSECONDLY: [1, 2, 5, 10, 20, 50, 100, 200, 500,
 | 
						|
                                1000, 2000, 5000, 10000, 20000, 50000,
 | 
						|
                                100000, 200000, 500000, 1000000],
 | 
						|
            }
 | 
						|
 | 
						|
        where the keys are defined in `dateutil.rrule`.
 | 
						|
 | 
						|
        The interval is used to specify multiples that are appropriate for
 | 
						|
        the frequency of ticking. For instance, every 7 days is sensible
 | 
						|
        for daily ticks, but for minutes/seconds, 15 or 30 make sense.
 | 
						|
 | 
						|
        When customizing, you should only modify the values for the existing
 | 
						|
        keys. You should not add or delete entries.
 | 
						|
 | 
						|
        Example for forcing ticks every 3 hours::
 | 
						|
 | 
						|
            locator = AutoDateLocator()
 | 
						|
            locator.intervald[HOURLY] = [3]  # only show every 3 hours
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, tz=None, minticks=5, maxticks=None,
 | 
						|
                 interval_multiples=True):
 | 
						|
        """
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        tz : `datetime.tzinfo`
 | 
						|
            Ticks timezone.
 | 
						|
        minticks : int
 | 
						|
            The minimum number of ticks desired; controls whether ticks occur
 | 
						|
            yearly, monthly, etc.
 | 
						|
        maxticks : int
 | 
						|
            The maximum number of ticks desired; controls the interval between
 | 
						|
            ticks (ticking every other, every 3, etc.).  For fine-grained
 | 
						|
            control, this can be a dictionary mapping individual rrule
 | 
						|
            frequency constants (YEARLY, MONTHLY, etc.) to their own maximum
 | 
						|
            number of ticks.  This can be used to keep the number of ticks
 | 
						|
            appropriate to the format chosen in `AutoDateFormatter`. Any
 | 
						|
            frequency not specified in this dictionary is given a default
 | 
						|
            value.
 | 
						|
        interval_multiples : bool, default: True
 | 
						|
            Whether ticks should be chosen to be multiple of the interval,
 | 
						|
            locking them to 'nicer' locations.  For example, this will force
 | 
						|
            the ticks to be at hours 0, 6, 12, 18 when hourly ticking is done
 | 
						|
            at 6 hour intervals.
 | 
						|
        """
 | 
						|
        DateLocator.__init__(self, tz)
 | 
						|
        self._freq = YEARLY
 | 
						|
        self._freqs = [YEARLY, MONTHLY, DAILY, HOURLY, MINUTELY,
 | 
						|
                       SECONDLY, MICROSECONDLY]
 | 
						|
        self.minticks = minticks
 | 
						|
 | 
						|
        self.maxticks = {YEARLY: 11, MONTHLY: 12, DAILY: 11, HOURLY: 12,
 | 
						|
                         MINUTELY: 11, SECONDLY: 11, MICROSECONDLY: 8}
 | 
						|
        if maxticks is not None:
 | 
						|
            try:
 | 
						|
                self.maxticks.update(maxticks)
 | 
						|
            except TypeError:
 | 
						|
                # Assume we were given an integer. Use this as the maximum
 | 
						|
                # number of ticks for every frequency and create a
 | 
						|
                # dictionary for this
 | 
						|
                self.maxticks = dict.fromkeys(self._freqs, maxticks)
 | 
						|
        self.interval_multiples = interval_multiples
 | 
						|
        self.intervald = {
 | 
						|
            YEARLY:   [1, 2, 4, 5, 10, 20, 40, 50, 100, 200, 400, 500,
 | 
						|
                       1000, 2000, 4000, 5000, 10000],
 | 
						|
            MONTHLY:  [1, 2, 3, 4, 6],
 | 
						|
            DAILY:    [1, 2, 3, 7, 14, 21],
 | 
						|
            HOURLY:   [1, 2, 3, 4, 6, 12],
 | 
						|
            MINUTELY: [1, 5, 10, 15, 30],
 | 
						|
            SECONDLY: [1, 5, 10, 15, 30],
 | 
						|
            MICROSECONDLY: [1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000,
 | 
						|
                            5000, 10000, 20000, 50000, 100000, 200000, 500000,
 | 
						|
                            1000000],
 | 
						|
                            }
 | 
						|
        if interval_multiples:
 | 
						|
            # Swap "3" for "4" in the DAILY list; If we use 3 we get bad
 | 
						|
            # tick loc for months w/ 31 days: 1, 4, ..., 28, 31, 1
 | 
						|
            # If we use 4 then we get: 1, 5, ... 25, 29, 1
 | 
						|
            self.intervald[DAILY] = [1, 2, 4, 7, 14, 21]
 | 
						|
 | 
						|
        self._byranges = [None, range(1, 13), range(1, 32),
 | 
						|
                          range(0, 24), range(0, 60), range(0, 60), None]
 | 
						|
 | 
						|
    def __call__(self):
 | 
						|
        # docstring inherited
 | 
						|
        dmin, dmax = self.viewlim_to_dt()
 | 
						|
        locator = self.get_locator(dmin, dmax)
 | 
						|
        return locator()
 | 
						|
 | 
						|
    def tick_values(self, vmin, vmax):
 | 
						|
        return self.get_locator(vmin, vmax).tick_values(vmin, vmax)
 | 
						|
 | 
						|
    def nonsingular(self, vmin, vmax):
 | 
						|
        # whatever is thrown at us, we can scale the unit.
 | 
						|
        # But default nonsingular date plots at an ~4 year period.
 | 
						|
        if not np.isfinite(vmin) or not np.isfinite(vmax):
 | 
						|
            # Except if there is no data, then use 2000-2010 as default.
 | 
						|
            return (date2num(datetime.date(2000, 1, 1)),
 | 
						|
                    date2num(datetime.date(2010, 1, 1)))
 | 
						|
        if vmax < vmin:
 | 
						|
            vmin, vmax = vmax, vmin
 | 
						|
        if vmin == vmax:
 | 
						|
            vmin = vmin - DAYS_PER_YEAR * 2
 | 
						|
            vmax = vmax + DAYS_PER_YEAR * 2
 | 
						|
        return vmin, vmax
 | 
						|
 | 
						|
    def _get_unit(self):
 | 
						|
        if self._freq in [MICROSECONDLY]:
 | 
						|
            return 1. / MUSECONDS_PER_DAY
 | 
						|
        else:
 | 
						|
            return RRuleLocator.get_unit_generic(self._freq)
 | 
						|
 | 
						|
    @cbook.deprecated("3.2")
 | 
						|
    def autoscale(self):
 | 
						|
        """Try to choose the view limits intelligently."""
 | 
						|
        dmin, dmax = self.datalim_to_dt()
 | 
						|
        return self.get_locator(dmin, dmax).autoscale()
 | 
						|
 | 
						|
    def get_locator(self, dmin, dmax):
 | 
						|
        """Pick the best locator based on a distance."""
 | 
						|
        delta = relativedelta(dmax, dmin)
 | 
						|
        tdelta = dmax - dmin
 | 
						|
 | 
						|
        # take absolute difference
 | 
						|
        if dmin > dmax:
 | 
						|
            delta = -delta
 | 
						|
            tdelta = -tdelta
 | 
						|
 | 
						|
        # The following uses a mix of calls to relativedelta and timedelta
 | 
						|
        # methods because there is incomplete overlap in the functionality of
 | 
						|
        # these similar functions, and it's best to avoid doing our own math
 | 
						|
        # whenever possible.
 | 
						|
        numYears = float(delta.years)
 | 
						|
        numMonths = numYears * MONTHS_PER_YEAR + delta.months
 | 
						|
        numDays = tdelta.days   # Avoids estimates of days/month, days/year
 | 
						|
        numHours = numDays * HOURS_PER_DAY + delta.hours
 | 
						|
        numMinutes = numHours * MIN_PER_HOUR + delta.minutes
 | 
						|
        numSeconds = np.floor(tdelta.total_seconds())
 | 
						|
        numMicroseconds = np.floor(tdelta.total_seconds() * 1e6)
 | 
						|
 | 
						|
        nums = [numYears, numMonths, numDays, numHours, numMinutes,
 | 
						|
                numSeconds, numMicroseconds]
 | 
						|
 | 
						|
        use_rrule_locator = [True] * 6 + [False]
 | 
						|
 | 
						|
        # Default setting of bymonth, etc. to pass to rrule
 | 
						|
        # [unused (for year), bymonth, bymonthday, byhour, byminute,
 | 
						|
        #  bysecond, unused (for microseconds)]
 | 
						|
        byranges = [None, 1, 1, 0, 0, 0, None]
 | 
						|
 | 
						|
        # Loop over all the frequencies and try to find one that gives at
 | 
						|
        # least a minticks tick positions.  Once this is found, look for
 | 
						|
        # an interval from an list specific to that frequency that gives no
 | 
						|
        # more than maxticks tick positions. Also, set up some ranges
 | 
						|
        # (bymonth, etc.) as appropriate to be passed to rrulewrapper.
 | 
						|
        for i, (freq, num) in enumerate(zip(self._freqs, nums)):
 | 
						|
            # If this particular frequency doesn't give enough ticks, continue
 | 
						|
            if num < self.minticks:
 | 
						|
                # Since we're not using this particular frequency, set
 | 
						|
                # the corresponding by_ to None so the rrule can act as
 | 
						|
                # appropriate
 | 
						|
                byranges[i] = None
 | 
						|
                continue
 | 
						|
 | 
						|
            # Find the first available interval that doesn't give too many
 | 
						|
            # ticks
 | 
						|
            for interval in self.intervald[freq]:
 | 
						|
                if num <= interval * (self.maxticks[freq] - 1):
 | 
						|
                    break
 | 
						|
            else:
 | 
						|
                # We went through the whole loop without breaking, default to
 | 
						|
                # the last interval in the list and raise a warning
 | 
						|
                cbook._warn_external(
 | 
						|
                    f"AutoDateLocator was unable to pick an appropriate "
 | 
						|
                    f"interval for this date range. It may be necessary to "
 | 
						|
                    f"add an interval value to the AutoDateLocator's "
 | 
						|
                    f"intervald dictionary. Defaulting to {interval}.")
 | 
						|
 | 
						|
            # Set some parameters as appropriate
 | 
						|
            self._freq = freq
 | 
						|
 | 
						|
            if self._byranges[i] and self.interval_multiples:
 | 
						|
                byranges[i] = self._byranges[i][::interval]
 | 
						|
                if i in (DAILY, WEEKLY):
 | 
						|
                    if interval == 14:
 | 
						|
                        # just make first and 15th.  Avoids 30th.
 | 
						|
                        byranges[i] = [1, 15]
 | 
						|
                    elif interval == 7:
 | 
						|
                        byranges[i] = [1, 8, 15, 22]
 | 
						|
 | 
						|
                interval = 1
 | 
						|
            else:
 | 
						|
                byranges[i] = self._byranges[i]
 | 
						|
            break
 | 
						|
        else:
 | 
						|
            interval = 1
 | 
						|
 | 
						|
        if (freq == YEARLY) and self.interval_multiples:
 | 
						|
            locator = YearLocator(interval, tz=self.tz)
 | 
						|
        elif use_rrule_locator[i]:
 | 
						|
            _, bymonth, bymonthday, byhour, byminute, bysecond, _ = byranges
 | 
						|
            rrule = rrulewrapper(self._freq, interval=interval,
 | 
						|
                                 dtstart=dmin, until=dmax,
 | 
						|
                                 bymonth=bymonth, bymonthday=bymonthday,
 | 
						|
                                 byhour=byhour, byminute=byminute,
 | 
						|
                                 bysecond=bysecond)
 | 
						|
 | 
						|
            locator = RRuleLocator(rrule, self.tz)
 | 
						|
        else:
 | 
						|
            locator = MicrosecondLocator(interval, tz=self.tz)
 | 
						|
            if date2num(dmin) > 70 * 365 and interval < 1000:
 | 
						|
                cbook._warn_external(
 | 
						|
                    'Plotting microsecond time intervals for dates far from '
 | 
						|
                    f'the epoch (time origin: {get_epoch()}) is not well-'
 | 
						|
                    'supported. See matplotlib.dates.set_epoch to change the '
 | 
						|
                    'epoch.')
 | 
						|
 | 
						|
        locator.set_axis(self.axis)
 | 
						|
 | 
						|
        if self.axis is not None:
 | 
						|
            locator.set_view_interval(*self.axis.get_view_interval())
 | 
						|
            locator.set_data_interval(*self.axis.get_data_interval())
 | 
						|
        return locator
 | 
						|
 | 
						|
 | 
						|
class YearLocator(DateLocator):
 | 
						|
    """
 | 
						|
    Make ticks on a given day of each year that is a multiple of base.
 | 
						|
 | 
						|
    Examples::
 | 
						|
 | 
						|
      # Tick every year on Jan 1st
 | 
						|
      locator = YearLocator()
 | 
						|
 | 
						|
      # Tick every 5 years on July 4th
 | 
						|
      locator = YearLocator(5, month=7, day=4)
 | 
						|
    """
 | 
						|
    def __init__(self, base=1, month=1, day=1, tz=None):
 | 
						|
        """
 | 
						|
        Mark years that are multiple of base on a given month and day
 | 
						|
        (default jan 1).
 | 
						|
        """
 | 
						|
        DateLocator.__init__(self, tz)
 | 
						|
        self.base = ticker._Edge_integer(base, 0)
 | 
						|
        self.replaced = {'month':  month,
 | 
						|
                         'day':    day,
 | 
						|
                         'hour':   0,
 | 
						|
                         'minute': 0,
 | 
						|
                         'second': 0,
 | 
						|
                         }
 | 
						|
        if not hasattr(tz, 'localize'):
 | 
						|
            # if tz is pytz, we need to do this w/ the localize fcn,
 | 
						|
            # otherwise datetime.replace works fine...
 | 
						|
            self.replaced['tzinfo'] = tz
 | 
						|
 | 
						|
    def __call__(self):
 | 
						|
        # if no data have been set, this will tank with a ValueError
 | 
						|
        try:
 | 
						|
            dmin, dmax = self.viewlim_to_dt()
 | 
						|
        except ValueError:
 | 
						|
            return []
 | 
						|
 | 
						|
        return self.tick_values(dmin, dmax)
 | 
						|
 | 
						|
    def tick_values(self, vmin, vmax):
 | 
						|
        ymin = self.base.le(vmin.year) * self.base.step
 | 
						|
        ymax = self.base.ge(vmax.year) * self.base.step
 | 
						|
 | 
						|
        vmin = vmin.replace(year=ymin, **self.replaced)
 | 
						|
        if hasattr(self.tz, 'localize'):
 | 
						|
            # look after pytz
 | 
						|
            if not vmin.tzinfo:
 | 
						|
                vmin = self.tz.localize(vmin, is_dst=True)
 | 
						|
 | 
						|
        ticks = [vmin]
 | 
						|
 | 
						|
        while True:
 | 
						|
            dt = ticks[-1]
 | 
						|
            if dt.year >= ymax:
 | 
						|
                return date2num(ticks)
 | 
						|
            year = dt.year + self.base.step
 | 
						|
            dt = dt.replace(year=year, **self.replaced)
 | 
						|
            if hasattr(self.tz, 'localize'):
 | 
						|
                # look after pytz
 | 
						|
                if not dt.tzinfo:
 | 
						|
                    dt = self.tz.localize(dt, is_dst=True)
 | 
						|
 | 
						|
            ticks.append(dt)
 | 
						|
 | 
						|
    @cbook.deprecated("3.2")
 | 
						|
    def autoscale(self):
 | 
						|
        """
 | 
						|
        Set the view limits to include the data range.
 | 
						|
        """
 | 
						|
        dmin, dmax = self.datalim_to_dt()
 | 
						|
 | 
						|
        ymin = self.base.le(dmin.year)
 | 
						|
        ymax = self.base.ge(dmax.year)
 | 
						|
        vmin = dmin.replace(year=ymin, **self.replaced)
 | 
						|
        vmin = vmin.astimezone(self.tz)
 | 
						|
        vmax = dmax.replace(year=ymax, **self.replaced)
 | 
						|
        vmax = vmax.astimezone(self.tz)
 | 
						|
 | 
						|
        vmin = date2num(vmin)
 | 
						|
        vmax = date2num(vmax)
 | 
						|
        return self.nonsingular(vmin, vmax)
 | 
						|
 | 
						|
 | 
						|
class MonthLocator(RRuleLocator):
 | 
						|
    """
 | 
						|
    Make ticks on occurrences of each month, e.g., 1, 3, 12.
 | 
						|
    """
 | 
						|
    def __init__(self, bymonth=None, bymonthday=1, interval=1, tz=None):
 | 
						|
        """
 | 
						|
        Mark every month in *bymonth*; *bymonth* can be an int or
 | 
						|
        sequence.  Default is ``range(1, 13)``, i.e. every month.
 | 
						|
 | 
						|
        *interval* is the interval between each iteration.  For
 | 
						|
        example, if ``interval=2``, mark every second occurrence.
 | 
						|
        """
 | 
						|
        if bymonth is None:
 | 
						|
            bymonth = range(1, 13)
 | 
						|
        elif isinstance(bymonth, np.ndarray):
 | 
						|
            # This fixes a bug in dateutil <= 2.3 which prevents the use of
 | 
						|
            # numpy arrays in (among other things) the bymonthday, byweekday
 | 
						|
            # and bymonth parameters.
 | 
						|
            bymonth = [x.item() for x in bymonth.astype(int)]
 | 
						|
 | 
						|
        rule = rrulewrapper(MONTHLY, bymonth=bymonth, bymonthday=bymonthday,
 | 
						|
                            interval=interval, **self.hms0d)
 | 
						|
        RRuleLocator.__init__(self, rule, tz)
 | 
						|
 | 
						|
 | 
						|
class WeekdayLocator(RRuleLocator):
 | 
						|
    """
 | 
						|
    Make ticks on occurrences of each weekday.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, byweekday=1, interval=1, tz=None):
 | 
						|
        """
 | 
						|
        Mark every weekday in *byweekday*; *byweekday* can be a number or
 | 
						|
        sequence.
 | 
						|
 | 
						|
        Elements of *byweekday* must be one of MO, TU, WE, TH, FR, SA,
 | 
						|
        SU, the constants from :mod:`dateutil.rrule`, which have been
 | 
						|
        imported into the :mod:`matplotlib.dates` namespace.
 | 
						|
 | 
						|
        *interval* specifies the number of weeks to skip.  For example,
 | 
						|
        ``interval=2`` plots every second week.
 | 
						|
        """
 | 
						|
        if isinstance(byweekday, np.ndarray):
 | 
						|
            # This fixes a bug in dateutil <= 2.3 which prevents the use of
 | 
						|
            # numpy arrays in (among other things) the bymonthday, byweekday
 | 
						|
            # and bymonth parameters.
 | 
						|
            [x.item() for x in byweekday.astype(int)]
 | 
						|
 | 
						|
        rule = rrulewrapper(DAILY, byweekday=byweekday,
 | 
						|
                            interval=interval, **self.hms0d)
 | 
						|
        RRuleLocator.__init__(self, rule, tz)
 | 
						|
 | 
						|
 | 
						|
class DayLocator(RRuleLocator):
 | 
						|
    """
 | 
						|
    Make ticks on occurrences of each day of the month.  For example,
 | 
						|
    1, 15, 30.
 | 
						|
    """
 | 
						|
    def __init__(self, bymonthday=None, interval=1, tz=None):
 | 
						|
        """
 | 
						|
        Mark every day in *bymonthday*; *bymonthday* can be an int or sequence.
 | 
						|
 | 
						|
        Default is to tick every day of the month: ``bymonthday=range(1, 32)``.
 | 
						|
        """
 | 
						|
        if interval != int(interval) or interval < 1:
 | 
						|
            raise ValueError("interval must be an integer greater than 0")
 | 
						|
        if bymonthday is None:
 | 
						|
            bymonthday = range(1, 32)
 | 
						|
        elif isinstance(bymonthday, np.ndarray):
 | 
						|
            # This fixes a bug in dateutil <= 2.3 which prevents the use of
 | 
						|
            # numpy arrays in (among other things) the bymonthday, byweekday
 | 
						|
            # and bymonth parameters.
 | 
						|
            bymonthday = [x.item() for x in bymonthday.astype(int)]
 | 
						|
 | 
						|
        rule = rrulewrapper(DAILY, bymonthday=bymonthday,
 | 
						|
                            interval=interval, **self.hms0d)
 | 
						|
        RRuleLocator.__init__(self, rule, tz)
 | 
						|
 | 
						|
 | 
						|
class HourLocator(RRuleLocator):
 | 
						|
    """
 | 
						|
    Make ticks on occurrences of each hour.
 | 
						|
    """
 | 
						|
    def __init__(self, byhour=None, interval=1, tz=None):
 | 
						|
        """
 | 
						|
        Mark every hour in *byhour*; *byhour* can be an int or sequence.
 | 
						|
        Default is to tick every hour: ``byhour=range(24)``
 | 
						|
 | 
						|
        *interval* is the interval between each iteration.  For
 | 
						|
        example, if ``interval=2``, mark every second occurrence.
 | 
						|
        """
 | 
						|
        if byhour is None:
 | 
						|
            byhour = range(24)
 | 
						|
 | 
						|
        rule = rrulewrapper(HOURLY, byhour=byhour, interval=interval,
 | 
						|
                            byminute=0, bysecond=0)
 | 
						|
        RRuleLocator.__init__(self, rule, tz)
 | 
						|
 | 
						|
 | 
						|
class MinuteLocator(RRuleLocator):
 | 
						|
    """
 | 
						|
    Make ticks on occurrences of each minute.
 | 
						|
    """
 | 
						|
    def __init__(self, byminute=None, interval=1, tz=None):
 | 
						|
        """
 | 
						|
        Mark every minute in *byminute*; *byminute* can be an int or
 | 
						|
        sequence.  Default is to tick every minute: ``byminute=range(60)``
 | 
						|
 | 
						|
        *interval* is the interval between each iteration.  For
 | 
						|
        example, if ``interval=2``, mark every second occurrence.
 | 
						|
        """
 | 
						|
        if byminute is None:
 | 
						|
            byminute = range(60)
 | 
						|
 | 
						|
        rule = rrulewrapper(MINUTELY, byminute=byminute, interval=interval,
 | 
						|
                            bysecond=0)
 | 
						|
        RRuleLocator.__init__(self, rule, tz)
 | 
						|
 | 
						|
 | 
						|
class SecondLocator(RRuleLocator):
 | 
						|
    """
 | 
						|
    Make ticks on occurrences of each second.
 | 
						|
    """
 | 
						|
    def __init__(self, bysecond=None, interval=1, tz=None):
 | 
						|
        """
 | 
						|
        Mark every second in *bysecond*; *bysecond* can be an int or
 | 
						|
        sequence.  Default is to tick every second: ``bysecond = range(60)``
 | 
						|
 | 
						|
        *interval* is the interval between each iteration.  For
 | 
						|
        example, if ``interval=2``, mark every second occurrence.
 | 
						|
 | 
						|
        """
 | 
						|
        if bysecond is None:
 | 
						|
            bysecond = range(60)
 | 
						|
 | 
						|
        rule = rrulewrapper(SECONDLY, bysecond=bysecond, interval=interval)
 | 
						|
        RRuleLocator.__init__(self, rule, tz)
 | 
						|
 | 
						|
 | 
						|
class MicrosecondLocator(DateLocator):
 | 
						|
    """
 | 
						|
    Make ticks on regular intervals of one or more microsecond(s).
 | 
						|
 | 
						|
    .. note::
 | 
						|
 | 
						|
        By default, Matplotlib uses a floating point representation of time in
 | 
						|
        days since the epoch, so plotting data with
 | 
						|
        microsecond time resolution does not work well for
 | 
						|
        dates that are far (about 70 years) from the epoch (check with
 | 
						|
        `~.dates.get_epoch`).
 | 
						|
 | 
						|
        If you want sub-microsecond resolution time plots, it is strongly
 | 
						|
        recommended to use floating point seconds, not datetime-like
 | 
						|
        time representation.
 | 
						|
 | 
						|
        If you really must use datetime.datetime() or similar and still
 | 
						|
        need microsecond precision, change the time origin via
 | 
						|
        `.dates.set_epoch` to something closer to the dates being plotted.
 | 
						|
        See :doc:`/gallery/ticks_and_spines/date_precision_and_epochs`.
 | 
						|
 | 
						|
    """
 | 
						|
    def __init__(self, interval=1, tz=None):
 | 
						|
        """
 | 
						|
        *interval* is the interval between each iteration.  For
 | 
						|
        example, if ``interval=2``, mark every second microsecond.
 | 
						|
 | 
						|
        """
 | 
						|
        self._interval = interval
 | 
						|
        self._wrapped_locator = ticker.MultipleLocator(interval)
 | 
						|
        self.tz = tz
 | 
						|
 | 
						|
    def set_axis(self, axis):
 | 
						|
        self._wrapped_locator.set_axis(axis)
 | 
						|
        return DateLocator.set_axis(self, axis)
 | 
						|
 | 
						|
    def set_view_interval(self, vmin, vmax):
 | 
						|
        self._wrapped_locator.set_view_interval(vmin, vmax)
 | 
						|
        return DateLocator.set_view_interval(self, vmin, vmax)
 | 
						|
 | 
						|
    def set_data_interval(self, vmin, vmax):
 | 
						|
        self._wrapped_locator.set_data_interval(vmin, vmax)
 | 
						|
        return DateLocator.set_data_interval(self, vmin, vmax)
 | 
						|
 | 
						|
    def __call__(self):
 | 
						|
        # if no data have been set, this will tank with a ValueError
 | 
						|
        try:
 | 
						|
            dmin, dmax = self.viewlim_to_dt()
 | 
						|
        except ValueError:
 | 
						|
            return []
 | 
						|
 | 
						|
        return self.tick_values(dmin, dmax)
 | 
						|
 | 
						|
    def tick_values(self, vmin, vmax):
 | 
						|
        nmin, nmax = date2num((vmin, vmax))
 | 
						|
        t0 = np.floor(nmin)
 | 
						|
        nmax = nmax - t0
 | 
						|
        nmin = nmin - t0
 | 
						|
        nmin *= MUSECONDS_PER_DAY
 | 
						|
        nmax *= MUSECONDS_PER_DAY
 | 
						|
 | 
						|
        ticks = self._wrapped_locator.tick_values(nmin, nmax)
 | 
						|
 | 
						|
        ticks = ticks / MUSECONDS_PER_DAY + t0
 | 
						|
        return ticks
 | 
						|
 | 
						|
    def _get_unit(self):
 | 
						|
        # docstring inherited
 | 
						|
        return 1. / MUSECONDS_PER_DAY
 | 
						|
 | 
						|
    def _get_interval(self):
 | 
						|
        # docstring inherited
 | 
						|
        return self._interval
 | 
						|
 | 
						|
 | 
						|
def epoch2num(e):
 | 
						|
    """
 | 
						|
    Convert UNIX time to days since Matplotlib epoch.
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    e : list of floats
 | 
						|
        Time in seconds since 1970-01-01.
 | 
						|
 | 
						|
    Returns
 | 
						|
    -------
 | 
						|
    `numpy.array`
 | 
						|
        Time in days since Matplotlib epoch (see `~.dates.get_epoch()`).
 | 
						|
    """
 | 
						|
 | 
						|
    dt = (np.datetime64('1970-01-01T00:00:00', 's') -
 | 
						|
          np.datetime64(get_epoch(), 's')).astype(float)
 | 
						|
 | 
						|
    return (dt + np.asarray(e)) / SEC_PER_DAY
 | 
						|
 | 
						|
 | 
						|
def num2epoch(d):
 | 
						|
    """
 | 
						|
    Convert days since Matplotlib epoch to UNIX time.
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    d : list of floats
 | 
						|
        Time in days since Matplotlib epoch (see `~.dates.get_epoch()`).
 | 
						|
 | 
						|
    Returns
 | 
						|
    -------
 | 
						|
    `numpy.array`
 | 
						|
        Time in seconds since 1970-01-01.
 | 
						|
    """
 | 
						|
    dt = (np.datetime64('1970-01-01T00:00:00', 's') -
 | 
						|
          np.datetime64(get_epoch(), 's')).astype(float)
 | 
						|
 | 
						|
    return np.asarray(d) * SEC_PER_DAY - dt
 | 
						|
 | 
						|
 | 
						|
@cbook.deprecated("3.2")
 | 
						|
def mx2num(mxdates):
 | 
						|
    """
 | 
						|
    Convert mx :class:`datetime` instance (or sequence of mx
 | 
						|
    instances) to the new date format.
 | 
						|
    """
 | 
						|
    scalar = False
 | 
						|
    if not np.iterable(mxdates):
 | 
						|
        scalar = True
 | 
						|
        mxdates = [mxdates]
 | 
						|
    ret = epoch2num([m.ticks() for m in mxdates])
 | 
						|
    if scalar:
 | 
						|
        return ret[0]
 | 
						|
    else:
 | 
						|
        return ret
 | 
						|
 | 
						|
 | 
						|
def date_ticker_factory(span, tz=None, numticks=5):
 | 
						|
    """
 | 
						|
    Create a date locator with *numticks* (approx) and a date formatter
 | 
						|
    for *span* in days.  Return value is (locator, formatter).
 | 
						|
    """
 | 
						|
 | 
						|
    if span == 0:
 | 
						|
        span = 1 / HOURS_PER_DAY
 | 
						|
 | 
						|
    mins = span * MINUTES_PER_DAY
 | 
						|
    hrs = span * HOURS_PER_DAY
 | 
						|
    days = span
 | 
						|
    wks = span / DAYS_PER_WEEK
 | 
						|
    months = span / DAYS_PER_MONTH      # Approx
 | 
						|
    years = span / DAYS_PER_YEAR        # Approx
 | 
						|
 | 
						|
    if years > numticks:
 | 
						|
        locator = YearLocator(int(years / numticks), tz=tz)  # define
 | 
						|
        fmt = '%Y'
 | 
						|
    elif months > numticks:
 | 
						|
        locator = MonthLocator(tz=tz)
 | 
						|
        fmt = '%b %Y'
 | 
						|
    elif wks > numticks:
 | 
						|
        locator = WeekdayLocator(tz=tz)
 | 
						|
        fmt = '%a, %b %d'
 | 
						|
    elif days > numticks:
 | 
						|
        locator = DayLocator(interval=math.ceil(days / numticks), tz=tz)
 | 
						|
        fmt = '%b %d'
 | 
						|
    elif hrs > numticks:
 | 
						|
        locator = HourLocator(interval=math.ceil(hrs / numticks), tz=tz)
 | 
						|
        fmt = '%H:%M\n%b %d'
 | 
						|
    elif mins > numticks:
 | 
						|
        locator = MinuteLocator(interval=math.ceil(mins / numticks), tz=tz)
 | 
						|
        fmt = '%H:%M:%S'
 | 
						|
    else:
 | 
						|
        locator = MinuteLocator(tz=tz)
 | 
						|
        fmt = '%H:%M:%S'
 | 
						|
 | 
						|
    formatter = DateFormatter(fmt, tz=tz)
 | 
						|
    return locator, formatter
 | 
						|
 | 
						|
 | 
						|
class DateConverter(units.ConversionInterface):
 | 
						|
    """
 | 
						|
    Converter for `datetime.date` and `datetime.datetime` data, or for
 | 
						|
    date/time data represented as it would be converted by `date2num`.
 | 
						|
 | 
						|
    The 'unit' tag for such data is None or a tzinfo instance.
 | 
						|
    """
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def axisinfo(unit, axis):
 | 
						|
        """
 | 
						|
        Return the `~matplotlib.units.AxisInfo` for *unit*.
 | 
						|
 | 
						|
        *unit* is a tzinfo instance or None.
 | 
						|
        The *axis* argument is required but not used.
 | 
						|
        """
 | 
						|
        tz = unit
 | 
						|
 | 
						|
        majloc = AutoDateLocator(tz=tz)
 | 
						|
        majfmt = AutoDateFormatter(majloc, tz=tz)
 | 
						|
        datemin = datetime.date(2000, 1, 1)
 | 
						|
        datemax = datetime.date(2010, 1, 1)
 | 
						|
 | 
						|
        return units.AxisInfo(majloc=majloc, majfmt=majfmt, label='',
 | 
						|
                              default_limits=(datemin, datemax))
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def convert(value, unit, axis):
 | 
						|
        """
 | 
						|
        If *value* is not already a number or sequence of numbers, convert it
 | 
						|
        with `date2num`.
 | 
						|
 | 
						|
        The *unit* and *axis* arguments are not used.
 | 
						|
        """
 | 
						|
        return date2num(value)
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def default_units(x, axis):
 | 
						|
        """
 | 
						|
        Return the tzinfo instance of *x* or of its first element, or None
 | 
						|
        """
 | 
						|
        if isinstance(x, np.ndarray):
 | 
						|
            x = x.ravel()
 | 
						|
 | 
						|
        try:
 | 
						|
            x = cbook.safe_first_element(x)
 | 
						|
        except (TypeError, StopIteration):
 | 
						|
            pass
 | 
						|
 | 
						|
        try:
 | 
						|
            return x.tzinfo
 | 
						|
        except AttributeError:
 | 
						|
            pass
 | 
						|
        return None
 | 
						|
 | 
						|
 | 
						|
class ConciseDateConverter(DateConverter):
 | 
						|
    # docstring inherited
 | 
						|
 | 
						|
    def __init__(self, formats=None, zero_formats=None, offset_formats=None,
 | 
						|
                 show_offset=True):
 | 
						|
        self._formats = formats
 | 
						|
        self._zero_formats = zero_formats
 | 
						|
        self._offset_formats = offset_formats
 | 
						|
        self._show_offset = show_offset
 | 
						|
        super().__init__()
 | 
						|
 | 
						|
    def axisinfo(self, unit, axis):
 | 
						|
        # docstring inherited
 | 
						|
        tz = unit
 | 
						|
        majloc = AutoDateLocator(tz=tz)
 | 
						|
        majfmt = ConciseDateFormatter(majloc, tz=tz, formats=self._formats,
 | 
						|
                                      zero_formats=self._zero_formats,
 | 
						|
                                      offset_formats=self._offset_formats,
 | 
						|
                                      show_offset=self._show_offset)
 | 
						|
        datemin = datetime.date(2000, 1, 1)
 | 
						|
        datemax = datetime.date(2010, 1, 1)
 | 
						|
        return units.AxisInfo(majloc=majloc, majfmt=majfmt, label='',
 | 
						|
                              default_limits=(datemin, datemax))
 | 
						|
 | 
						|
 | 
						|
units.registry[np.datetime64] = DateConverter()
 | 
						|
units.registry[datetime.date] = DateConverter()
 | 
						|
units.registry[datetime.datetime] = DateConverter()
 |