1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945 |
- """
- 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 _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
- # make an iterable, but save state to unpack later:
- iterable = np.iterable(d)
- if not iterable:
- d = [d]
- d = np.asarray(d)
- # convert to datetime64 arrays, if not already:
- if not np.issubdtype(d.dtype, np.datetime64):
- # datetime arrays
- if not d.size:
- # deals with an empty array...
- return d
- tzi = getattr(d[0], 'tzinfo', None)
- if tzi is not None:
- # make datetime naive:
- d = [dt.astimezone(UTC).replace(tzinfo=None) for dt in d]
- d = np.asarray(d)
- d = d.astype('datetime64[us]')
- d = _dt64_to_ordinalf(d)
- return d if iterable else d[0]
- 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()
|