1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894 |
- """
- Matplotlib provides sophisticated date plotting capabilities, standing on the
- shoulders of python :mod:`datetime` and the add-on module dateutil_.
- By default, Matplotlib uses the units machinery described in
- `~matplotlib.units` to convert `datetime.datetime`, and `numpy.datetime64`
- objects when plotted on an x- or y-axis. The user does not
- need to do anything for dates to be formatted, but dates often have strict
- formatting needs, so this module provides many tick locators and formatters.
- A basic example using `numpy.datetime64` is::
- import numpy as np
- times = np.arange(np.datetime64('2001-01-02'),
- np.datetime64('2002-02-03'), np.timedelta64(75, 'm'))
- y = np.random.randn(len(times))
- fig, ax = plt.subplots()
- ax.plot(times, y)
- .. seealso::
- - :doc:`/gallery/text_labels_and_annotations/date`
- - :doc:`/gallery/ticks/date_concise_formatter`
- - :doc:`/gallery/ticks/date_demo_convert`
- .. _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/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, locators and formatters are timezone aware.
- If no explicit timezone is provided, :rc:`timezone` is assumed, provided as a
- string. If you want to use a different timezone, pass the *tz* keyword
- argument of `num2date` to any date tick locators or formatters you create. This
- can be either a `datetime.tzinfo` instance or a string with the timezone name
- that can be parsed by `~dateutil.tz.gettz`.
- 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-locators:
- Date tick locators
- ------------------
- Most of the date tick locators can locate single or multiple ticks. 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 tick locators 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 `rrulewrapper`.
- `rrulewrapper` is a simple wrapper around dateutil_'s `dateutil.rrule`
- which allow almost arbitrary date tick specifications.
- See :doc:`rrule example </gallery/ticks/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. For example, 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:
- 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.
- """
- import datetime
- import functools
- import logging
- 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 as mpl
- from matplotlib import _api, cbook, ticker, units
- __all__ = ('datestr2num', 'date2num', 'num2date', 'num2timedelta', 'drange',
- 'set_epoch', 'get_epoch', 'DateFormatter', 'ConciseDateFormatter',
- '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', 'rrulewrapper')
- _log = logging.getLogger(__name__)
- UTC = datetime.timezone.utc
- @_api.caching_module_getattr
- class __getattr__:
- JULIAN_OFFSET = _api.deprecated("3.7")(property(lambda self: 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
- def _get_tzinfo(tz=None):
- """
- Generate `~datetime.tzinfo` from a string or return `~datetime.tzinfo`.
- If None, retrieve the preferred timezone from the rcParams dictionary.
- """
- tz = mpl._val_or_rc(tz, 'timezone')
- if tz == 'UTC':
- return UTC
- if isinstance(tz, str):
- tzinfo = dateutil.tz.gettz(tz)
- if tzinfo is None:
- raise ValueError(f"{tz} is not a valid timezone as parsed by"
- " dateutil.tz.gettz.")
- return tzinfo
- if isinstance(tz, datetime.tzinfo):
- return tz
- raise TypeError(f"tz must be string or tzinfo subclass, not {tz!r}.")
- # Time-related constants.
- EPOCH_OFFSET = float(datetime.datetime(1970, 1, 1).toordinal())
- # EPOCH_OFFSET is not used by matplotlib
- 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 of 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/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
- _epoch = mpl._val_or_rc(_epoch, 'date.epoch')
- return _epoch
- def _dt64_to_ordinalf(d):
- """
- Convert `numpy.datetime64` or an `numpy.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)
- dt[d_int == NaT_int] = 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`.
- """
- tz = _get_tzinfo(tz)
- 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 = [date2num(dateutil.parser.parse(s, default=default))
- for s in d]
- return np.asarray(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.
- """
- # Unpack in case of e.g. Pandas or xarray object
- d = cbook._unpack_to_numpy(d)
- # make an iterable, but save state to unpack later:
- iterable = np.iterable(d)
- if not iterable:
- d = [d]
- masked = np.ma.is_masked(d)
- mask = np.ma.getmask(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 = np.ma.masked_array(d, mask=mask) if masked else d
- d = _dt64_to_ordinalf(d)
- return d if iterable else d[0]
- @_api.deprecated("3.7")
- 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 = __getattr__("JULIAN_OFFSET") - ep0 + ep
- return np.subtract(j, dt) # Handles both scalar & nonscalar j.
- @_api.deprecated("3.7")
- 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 = __getattr__("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 or `~datetime.tzinfo`, default: :rc:`timezone`
- Timezone of *x*. If a string, *tz* is passed to `dateutil.tz`.
- 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 Gregorian calendar is assumed; this is not universal practice.
- For details, see the module docstring.
- """
- tz = _get_tzinfo(tz)
- 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 or equal to dend,
- # just subtract one delta
- dinterval_end -= delta
- num -= 1
- f2 = date2num(dinterval_end) # new float-endpoint
- return np.linspace(f1, f2, num + 1)
- def _wrap_in_tex(text):
- p = r'([a-zA-Z]+)'
- ret_text = re.sub(p, r'}$\1$\\mathdefault{', text)
- # Braces ensure symbols are not spaced like binary operators.
- ret_text = ret_text.replace('-', '{-}').replace(':', '{:}')
- # To not concatenate space between numbers.
- ret_text = ret_text.replace(' ', r'\;')
- ret_text = '$\\mathdefault{' + ret_text + '}$'
- ret_text = ret_text.replace('$\\mathdefault{}$', '')
- return ret_text
- ## date tick locators and formatters ###
- class DateFormatter(ticker.Formatter):
- """
- Format a tick (in days since the epoch) with a
- `~datetime.datetime.strftime` format string.
- """
- def __init__(self, fmt, tz=None, *, usetex=None):
- """
- Parameters
- ----------
- fmt : str
- `~datetime.datetime.strftime` format string
- tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
- Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
- usetex : bool, default: :rc:`text.usetex`
- To enable/disable the use of TeX's math mode for rendering the
- results of the formatter.
- """
- self.tz = _get_tzinfo(tz)
- self.fmt = fmt
- self._usetex = mpl._val_or_rc(usetex, 'text.usetex')
- def __call__(self, x, pos=0):
- result = num2date(x, self.tz).strftime(self.fmt)
- return _wrap_in_tex(result) if self._usetex else result
- def set_tzinfo(self, tz):
- self.tz = _get_tzinfo(tz)
- 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 or `~datetime.tzinfo`, default: :rc:`timezone`
- Ticks timezone, passed to `.dates.num2date`.
- 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.
- usetex : bool, default: :rc:`text.usetex`
- To enable/disable the use of TeX's math mode for rendering the results
- of the formatter.
- Examples
- --------
- See :doc:`/gallery/ticks/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, *, usetex=None):
- """
- 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('offset_formats 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
- self._usetex = mpl._val_or_rc(usetex, 'text.usetex')
- def __call__(self, x, pos=None):
- formatter = DateFormatter(self.defaultfmt, self._tz,
- usetex=self._usetex)
- 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
- show_offset = self.show_offset
- # 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):
- unique = np.unique(tickdate[:, level])
- if len(unique) > 1:
- # if 1 is included in unique, the year is shown in ticks
- if level < 2 and np.any(unique == 1):
- show_offset = False
- break
- elif level == 0:
- # all tickdate are the same, so only micros might be different
- # set to the most precise (6: microseconds doesn't exist...)
- level = 5
- # 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 show_offset:
- # set the offset string:
- self.offset_string = tickdatetime[-1].strftime(offsetfmts[level])
- if self._usetex:
- self.offset_string = _wrap_in_tex(self.offset_string)
- else:
- self.offset_string = ''
- if self._usetex:
- return [_wrap_in_tex(l) for l in labels]
- else:
- 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`.
- `.AutoDateFormatter` has a ``.scale`` dictionary that maps tick scales (the
- interval in days between one major tick) to format strings; this dictionary
- defaults to ::
- 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'],
- }
- The formatter uses the format string corresponding to the lowest key in
- the dictionary that is greater or equal to the current scale. Dictionary
- entries can be customized::
- locator = AutoDateLocator()
- formatter = AutoDateFormatter(locator)
- formatter.scaled[1/(24*60)] = '%M:%S' # only show min and sec
- Custom callables can also be used instead of format strings. 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
- formatter.scaled[1/(24*60)] = 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', *,
- usetex=None):
- """
- Autoformat the date labels.
- Parameters
- ----------
- locator : `.ticker.Locator`
- Locator that this axis is using.
- tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
- Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
- defaultfmt : str
- The default format to use if none of the values in ``self.scaled``
- are greater than the unit returned by ``locator._get_unit()``.
- usetex : bool, default: :rc:`text.usetex`
- To enable/disable the use of TeX's math mode for rendering the
- results of the formatter. If any entries in ``self.scaled`` are set
- as functions, then it is up to the customized function to enable or
- disable TeX's math mode itself.
- """
- self._locator = locator
- self._tz = tz
- self.defaultfmt = defaultfmt
- self._formatter = DateFormatter(self.defaultfmt, tz)
- rcParams = mpl.rcParams
- self._usetex = mpl._val_or_rc(usetex, 'text.usetex')
- 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, usetex=self._usetex)
- result = self._formatter(x, pos)
- elif callable(fmt):
- result = fmt(x, pos)
- else:
- raise TypeError(f'Unexpected type passed to {self!r}.')
- return result
- class rrulewrapper:
- """
- A simple wrapper around a `dateutil.rrule` allowing flexible
- date tick specifications.
- """
- def __init__(self, freq, tzinfo=None, **kwargs):
- """
- Parameters
- ----------
- freq : {YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, SECONDLY}
- Tick frequency. These constants are defined in `dateutil.rrule`,
- but they are accessible from `matplotlib.dates` as well.
- tzinfo : `datetime.tzinfo`, optional
- Time zone information. The default is None.
- **kwargs
- Additional keyword arguments are passed to the `dateutil.rrule`.
- """
- kwargs['freq'] = freq
- self._base_tzinfo = tzinfo
- self._update_rrule(**kwargs)
- def set(self, **kwargs):
- """Set parameters for an existing wrapper."""
- self._construct.update(kwargs)
- self._update_rrule(**self._construct)
- def _update_rrule(self, **kwargs):
- tzinfo = self._base_tzinfo
- # rrule does not play nicely with timezones - 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 : str or `~datetime.tzinfo`, default: :rc:`timezone`
- Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
- """
- self.tz = _get_tzinfo(tz)
- def set_tzinfo(self, tz):
- """
- Set timezone info.
- Parameters
- ----------
- tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
- Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
- """
- self.tz = _get_tzinfo(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 1970 as default.
- return (date2num(datetime.date(1970, 1, 1)),
- date2num(datetime.date(1970, 1, 2)))
- 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):
- super().__init__(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):
- start, stop = self._create_rrule(vmin, vmax)
- dates = self.rule.between(start, stop, True)
- if len(dates) == 0:
- return date2num([vmin, vmax])
- return self.raise_if_exceeds(date2num(dates))
- def _create_rrule(self, vmin, vmax):
- # set appropriate rrule dtstart and until and return
- # start and end
- 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)
- return vmin, vmax
- 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
- 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 : str or `~datetime.tzinfo`, default: :rc:`timezone`
- Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
- 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.
- """
- super().__init__(tz=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]
- 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 1970 as default.
- return (date2num(datetime.date(1970, 1, 1)),
- date2num(datetime.date(1970, 1, 2)))
- 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)
- 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 a 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:
- if not (self.interval_multiples and freq == DAILY):
- _api.warn_external(
- f"AutoDateLocator was unable to pick an appropriate "
- f"interval for this date range. It may be necessary "
- f"to 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, tz=self.tz)
- else:
- locator = MicrosecondLocator(interval, tz=self.tz)
- if date2num(dmin) > 70 * 365 and interval < 1000:
- _api.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)
- return locator
- class YearLocator(RRuleLocator):
- """
- 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):
- """
- Parameters
- ----------
- base : int, default: 1
- Mark ticks every *base* years.
- month : int, default: 1
- The month on which to place the ticks, starting from 1. Default is
- January.
- day : int, default: 1
- The day on which to place the ticks.
- tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
- Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
- """
- rule = rrulewrapper(YEARLY, interval=base, bymonth=month,
- bymonthday=day, **self.hms0d)
- super().__init__(rule, tz=tz)
- self.base = ticker._Edge_integer(base, 0)
- def _create_rrule(self, vmin, vmax):
- # 'start' needs to be a multiple of the interval to create ticks on
- # interval multiples when the tick frequency is YEARLY
- ymin = max(self.base.le(vmin.year) * self.base.step, 1)
- ymax = min(self.base.ge(vmax.year) * self.base.step, 9999)
- c = self.rule._construct
- replace = {'year': ymin,
- 'month': c.get('bymonth', 1),
- 'day': c.get('bymonthday', 1),
- 'hour': 0, 'minute': 0, 'second': 0}
- start = vmin.replace(**replace)
- stop = start.replace(year=ymax)
- self.rule.set(dtstart=start, until=stop)
- return start, stop
- 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):
- """
- Parameters
- ----------
- bymonth : int or list of int, default: all months
- Ticks will be placed on every month in *bymonth*. Default is
- ``range(1, 13)``, i.e. every month.
- bymonthday : int, default: 1
- The day on which to place the ticks.
- interval : int, default: 1
- The interval between each iteration. For example, if
- ``interval=2``, mark every second occurrence.
- tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
- Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
- """
- if bymonth is None:
- bymonth = range(1, 13)
- rule = rrulewrapper(MONTHLY, bymonth=bymonth, bymonthday=bymonthday,
- interval=interval, **self.hms0d)
- super().__init__(rule, tz=tz)
- class WeekdayLocator(RRuleLocator):
- """
- Make ticks on occurrences of each weekday.
- """
- def __init__(self, byweekday=1, interval=1, tz=None):
- """
- Parameters
- ----------
- byweekday : int or list of int, default: all days
- Ticks will be placed on every weekday in *byweekday*. Default is
- every day.
- 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 : int, default: 1
- The interval between each iteration. For example, if
- ``interval=2``, mark every second occurrence.
- tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
- Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
- """
- rule = rrulewrapper(DAILY, byweekday=byweekday,
- interval=interval, **self.hms0d)
- super().__init__(rule, tz=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):
- """
- Parameters
- ----------
- bymonthday : int or list of int, default: all days
- Ticks will be placed on every day in *bymonthday*. Default is
- ``bymonthday=range(1, 32)``, i.e., every day of the month.
- interval : int, default: 1
- The interval between each iteration. For example, if
- ``interval=2``, mark every second occurrence.
- tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
- Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
- """
- 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)
- rule = rrulewrapper(DAILY, bymonthday=bymonthday,
- interval=interval, **self.hms0d)
- super().__init__(rule, tz=tz)
- class HourLocator(RRuleLocator):
- """
- Make ticks on occurrences of each hour.
- """
- def __init__(self, byhour=None, interval=1, tz=None):
- """
- Parameters
- ----------
- byhour : int or list of int, default: all hours
- Ticks will be placed on every hour in *byhour*. Default is
- ``byhour=range(24)``, i.e., every hour.
- interval : int, default: 1
- The interval between each iteration. For example, if
- ``interval=2``, mark every second occurrence.
- tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
- Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
- """
- if byhour is None:
- byhour = range(24)
- rule = rrulewrapper(HOURLY, byhour=byhour, interval=interval,
- byminute=0, bysecond=0)
- super().__init__(rule, tz=tz)
- class MinuteLocator(RRuleLocator):
- """
- Make ticks on occurrences of each minute.
- """
- def __init__(self, byminute=None, interval=1, tz=None):
- """
- Parameters
- ----------
- byminute : int or list of int, default: all minutes
- Ticks will be placed on every minute in *byminute*. Default is
- ``byminute=range(60)``, i.e., every minute.
- interval : int, default: 1
- The interval between each iteration. For example, if
- ``interval=2``, mark every second occurrence.
- tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
- Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
- """
- if byminute is None:
- byminute = range(60)
- rule = rrulewrapper(MINUTELY, byminute=byminute, interval=interval,
- bysecond=0)
- super().__init__(rule, tz=tz)
- class SecondLocator(RRuleLocator):
- """
- Make ticks on occurrences of each second.
- """
- def __init__(self, bysecond=None, interval=1, tz=None):
- """
- Parameters
- ----------
- bysecond : int or list of int, default: all seconds
- Ticks will be placed on every second in *bysecond*. Default is
- ``bysecond = range(60)``, i.e., every second.
- interval : int, default: 1
- The interval between each iteration. For example, if
- ``interval=2``, mark every second occurrence.
- tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
- Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
- """
- if bysecond is None:
- bysecond = range(60)
- rule = rrulewrapper(SECONDLY, bysecond=bysecond, interval=interval)
- super().__init__(rule, tz=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/date_precision_and_epochs`.
- """
- def __init__(self, interval=1, tz=None):
- """
- Parameters
- ----------
- interval : int, default: 1
- The interval between each iteration. For example, if
- ``interval=2``, mark every second occurrence.
- tz : str or `~datetime.tzinfo`, default: :rc:`timezone`
- Ticks timezone. If a string, *tz* is passed to `dateutil.tz`.
- """
- super().__init__(tz=tz)
- self._interval = interval
- self._wrapped_locator = ticker.MultipleLocator(interval)
- def set_axis(self, axis):
- self._wrapped_locator.set_axis(axis)
- return super().set_axis(axis)
- 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
- 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 `~datetime.tzinfo` instance.
- """
- def __init__(self, *, interval_multiples=True):
- self._interval_multiples = interval_multiples
- super().__init__()
- def axisinfo(self, unit, axis):
- """
- Return the `~matplotlib.units.AxisInfo` for *unit*.
- *unit* is a `~datetime.tzinfo` instance or None.
- The *axis* argument is required but not used.
- """
- tz = unit
- majloc = AutoDateLocator(tz=tz,
- interval_multiples=self._interval_multiples)
- majfmt = AutoDateFormatter(majloc, tz=tz)
- datemin = datetime.date(1970, 1, 1)
- datemax = datetime.date(1970, 1, 2)
- 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 `~datetime.tzinfo` instance of *x* or of its first element,
- or None
- """
- if isinstance(x, np.ndarray):
- x = x.ravel()
- try:
- x = cbook._safe_first_finite(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, *, interval_multiples=True):
- self._formats = formats
- self._zero_formats = zero_formats
- self._offset_formats = offset_formats
- self._show_offset = show_offset
- self._interval_multiples = interval_multiples
- super().__init__()
- def axisinfo(self, unit, axis):
- # docstring inherited
- tz = unit
- majloc = AutoDateLocator(tz=tz,
- interval_multiples=self._interval_multiples)
- 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(1970, 1, 1)
- datemax = datetime.date(1970, 1, 2)
- return units.AxisInfo(majloc=majloc, majfmt=majfmt, label='',
- default_limits=(datemin, datemax))
- class _SwitchableDateConverter:
- """
- Helper converter-like object that generates and dispatches to
- temporary ConciseDateConverter or DateConverter instances based on
- :rc:`date.converter` and :rc:`date.interval_multiples`.
- """
- @staticmethod
- def _get_converter():
- converter_cls = {
- "concise": ConciseDateConverter, "auto": DateConverter}[
- mpl.rcParams["date.converter"]]
- interval_multiples = mpl.rcParams["date.interval_multiples"]
- return converter_cls(interval_multiples=interval_multiples)
- def axisinfo(self, *args, **kwargs):
- return self._get_converter().axisinfo(*args, **kwargs)
- def default_units(self, *args, **kwargs):
- return self._get_converter().default_units(*args, **kwargs)
- def convert(self, *args, **kwargs):
- return self._get_converter().convert(*args, **kwargs)
- units.registry[np.datetime64] = \
- units.registry[datetime.date] = \
- units.registry[datetime.datetime] = \
- _SwitchableDateConverter()
|