Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/matplotlib/dates.py: 22%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

604 statements  

1""" 

2Matplotlib provides sophisticated date plotting capabilities, standing on the 

3shoulders of python :mod:`datetime` and the add-on module dateutil_. 

4 

5By default, Matplotlib uses the units machinery described in 

6`~matplotlib.units` to convert `datetime.datetime`, and `numpy.datetime64` 

7objects when plotted on an x- or y-axis. The user does not 

8need to do anything for dates to be formatted, but dates often have strict 

9formatting needs, so this module provides many tick locators and formatters. 

10A basic example using `numpy.datetime64` is:: 

11 

12 import numpy as np 

13 

14 times = np.arange(np.datetime64('2001-01-02'), 

15 np.datetime64('2002-02-03'), np.timedelta64(75, 'm')) 

16 y = np.random.randn(len(times)) 

17 

18 fig, ax = plt.subplots() 

19 ax.plot(times, y) 

20 

21.. seealso:: 

22 

23 - :doc:`/gallery/text_labels_and_annotations/date` 

24 - :doc:`/gallery/ticks/date_concise_formatter` 

25 - :doc:`/gallery/ticks/date_demo_convert` 

26 

27.. _date-format: 

28 

29Matplotlib date format 

30---------------------- 

31 

32Matplotlib represents dates using floating point numbers specifying the number 

33of days since a default epoch of 1970-01-01 UTC; for example, 

341970-01-01, 06:00 is the floating point number 0.25. The formatters and 

35locators require the use of `datetime.datetime` objects, so only dates between 

36year 0001 and 9999 can be represented. Microsecond precision 

37is achievable for (approximately) 70 years on either side of the epoch, and 

3820 microseconds for the rest of the allowable range of dates (year 0001 to 

399999). The epoch can be changed at import time via `.dates.set_epoch` or 

40:rc:`dates.epoch` to other dates if necessary; see 

41:doc:`/gallery/ticks/date_precision_and_epochs` for a discussion. 

42 

43.. note:: 

44 

45 Before Matplotlib 3.3, the epoch was 0000-12-31 which lost modern 

46 microsecond precision and also made the default axis limit of 0 an invalid 

47 datetime. In 3.3 the epoch was changed as above. To convert old 

48 ordinal floats to the new epoch, users can do:: 

49 

50 new_ordinal = old_ordinal + mdates.date2num(np.datetime64('0000-12-31')) 

51 

52 

53There are a number of helper functions to convert between :mod:`datetime` 

54objects and Matplotlib dates: 

55 

56.. currentmodule:: matplotlib.dates 

57 

58.. autosummary:: 

59 :nosignatures: 

60 

61 datestr2num 

62 date2num 

63 num2date 

64 num2timedelta 

65 drange 

66 set_epoch 

67 get_epoch 

68 

69.. note:: 

70 

71 Like Python's `datetime.datetime`, Matplotlib uses the Gregorian calendar 

72 for all conversions between dates and floating point numbers. This practice 

73 is not universal, and calendar differences can cause confusing 

74 differences between what Python and Matplotlib give as the number of days 

75 since 0001-01-01 and what other software and databases yield. For 

76 example, the US Naval Observatory uses a calendar that switches 

77 from Julian to Gregorian in October, 1582. Hence, using their 

78 calculator, the number of days between 0001-01-01 and 2006-04-01 is 

79 732403, whereas using the Gregorian calendar via the datetime 

80 module we find:: 

81 

82 In [1]: date(2006, 4, 1).toordinal() - date(1, 1, 1).toordinal() 

83 Out[1]: 732401 

84 

85All the Matplotlib date converters, locators and formatters are timezone aware. 

86If no explicit timezone is provided, :rc:`timezone` is assumed, provided as a 

87string. If you want to use a different timezone, pass the *tz* keyword 

88argument of `num2date` to any date tick locators or formatters you create. This 

89can be either a `datetime.tzinfo` instance or a string with the timezone name 

90that can be parsed by `~dateutil.tz.gettz`. 

91 

92A wide range of specific and general purpose date tick locators and 

93formatters are provided in this module. See 

94:mod:`matplotlib.ticker` for general information on tick locators 

95and formatters. These are described below. 

96 

97The dateutil_ module provides additional code to handle date ticking, making it 

98easy to place ticks on any kinds of dates. See examples below. 

99 

100.. _dateutil: https://dateutil.readthedocs.io 

101 

102.. _date-locators: 

103 

104Date tick locators 

105------------------ 

106 

107Most of the date tick locators can locate single or multiple ticks. For example:: 

108 

109 # import constants for the days of the week 

110 from matplotlib.dates import MO, TU, WE, TH, FR, SA, SU 

111 

112 # tick on Mondays every week 

113 loc = WeekdayLocator(byweekday=MO, tz=tz) 

114 

115 # tick on Mondays and Saturdays 

116 loc = WeekdayLocator(byweekday=(MO, SA)) 

117 

118In addition, most of the constructors take an interval argument:: 

119 

120 # tick on Mondays every second week 

121 loc = WeekdayLocator(byweekday=MO, interval=2) 

122 

123The rrule locator allows completely general date ticking:: 

124 

125 # tick every 5th easter 

126 rule = rrulewrapper(YEARLY, byeaster=1, interval=5) 

127 loc = RRuleLocator(rule) 

128 

129The available date tick locators are: 

130 

131* `MicrosecondLocator`: Locate microseconds. 

132 

133* `SecondLocator`: Locate seconds. 

134 

135* `MinuteLocator`: Locate minutes. 

136 

137* `HourLocator`: Locate hours. 

138 

139* `DayLocator`: Locate specified days of the month. 

140 

141* `WeekdayLocator`: Locate days of the week, e.g., MO, TU. 

142 

143* `MonthLocator`: Locate months, e.g., 7 for July. 

144 

145* `YearLocator`: Locate years that are multiples of base. 

146 

147* `RRuleLocator`: Locate using a `rrulewrapper`. 

148 `rrulewrapper` is a simple wrapper around dateutil_'s `dateutil.rrule` 

149 which allow almost arbitrary date tick specifications. 

150 See :doc:`rrule example </gallery/ticks/date_demo_rrule>`. 

151 

152* `AutoDateLocator`: On autoscale, this class picks the best `DateLocator` 

153 (e.g., `RRuleLocator`) to set the view limits and the tick locations. If 

154 called with ``interval_multiples=True`` it will make ticks line up with 

155 sensible multiples of the tick intervals. For example, if the interval is 

156 4 hours, it will pick hours 0, 4, 8, etc. as ticks. This behaviour is not 

157 guaranteed by default. 

158 

159.. _date-formatters: 

160 

161Date formatters 

162--------------- 

163 

164The available date formatters are: 

165 

166* `AutoDateFormatter`: attempts to figure out the best format to use. This is 

167 most useful when used with the `AutoDateLocator`. 

168 

169* `ConciseDateFormatter`: also attempts to figure out the best format to use, 

170 and to make the format as compact as possible while still having complete 

171 date information. This is most useful when used with the `AutoDateLocator`. 

172 

173* `DateFormatter`: use `~datetime.datetime.strftime` format strings. 

174""" 

175 

176import datetime 

177import functools 

178import logging 

179import re 

180 

181from dateutil.rrule import (rrule, MO, TU, WE, TH, FR, SA, SU, YEARLY, 

182 MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, 

183 SECONDLY) 

184from dateutil.relativedelta import relativedelta 

185import dateutil.parser 

186import dateutil.tz 

187import numpy as np 

188 

189import matplotlib as mpl 

190from matplotlib import _api, cbook, ticker, units 

191 

192__all__ = ('datestr2num', 'date2num', 'num2date', 'num2timedelta', 'drange', 

193 'set_epoch', 'get_epoch', 'DateFormatter', 'ConciseDateFormatter', 

194 'AutoDateFormatter', 'DateLocator', 'RRuleLocator', 

195 'AutoDateLocator', 'YearLocator', 'MonthLocator', 'WeekdayLocator', 

196 'DayLocator', 'HourLocator', 'MinuteLocator', 

197 'SecondLocator', 'MicrosecondLocator', 

198 'rrule', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU', 

199 'YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY', 

200 'HOURLY', 'MINUTELY', 'SECONDLY', 'MICROSECONDLY', 'relativedelta', 

201 'DateConverter', 'ConciseDateConverter', 'rrulewrapper') 

202 

203 

204_log = logging.getLogger(__name__) 

205UTC = datetime.timezone.utc 

206 

207 

208def _get_tzinfo(tz=None): 

209 """ 

210 Generate `~datetime.tzinfo` from a string or return `~datetime.tzinfo`. 

211 If None, retrieve the preferred timezone from the rcParams dictionary. 

212 """ 

213 tz = mpl._val_or_rc(tz, 'timezone') 

214 if tz == 'UTC': 

215 return UTC 

216 if isinstance(tz, str): 

217 tzinfo = dateutil.tz.gettz(tz) 

218 if tzinfo is None: 

219 raise ValueError(f"{tz} is not a valid timezone as parsed by" 

220 " dateutil.tz.gettz.") 

221 return tzinfo 

222 if isinstance(tz, datetime.tzinfo): 

223 return tz 

224 raise TypeError(f"tz must be string or tzinfo subclass, not {tz!r}.") 

225 

226 

227# Time-related constants. 

228EPOCH_OFFSET = float(datetime.datetime(1970, 1, 1).toordinal()) 

229# EPOCH_OFFSET is not used by matplotlib 

230MICROSECONDLY = SECONDLY + 1 

231HOURS_PER_DAY = 24. 

232MIN_PER_HOUR = 60. 

233SEC_PER_MIN = 60. 

234MONTHS_PER_YEAR = 12. 

235 

236DAYS_PER_WEEK = 7. 

237DAYS_PER_MONTH = 30. 

238DAYS_PER_YEAR = 365.0 

239 

240MINUTES_PER_DAY = MIN_PER_HOUR * HOURS_PER_DAY 

241 

242SEC_PER_HOUR = SEC_PER_MIN * MIN_PER_HOUR 

243SEC_PER_DAY = SEC_PER_HOUR * HOURS_PER_DAY 

244SEC_PER_WEEK = SEC_PER_DAY * DAYS_PER_WEEK 

245 

246MUSECONDS_PER_DAY = 1e6 * SEC_PER_DAY 

247 

248MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY = ( 

249 MO, TU, WE, TH, FR, SA, SU) 

250WEEKDAYS = (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) 

251 

252# default epoch: passed to np.datetime64... 

253_epoch = None 

254 

255 

256def _reset_epoch_test_example(): 

257 """ 

258 Reset the Matplotlib date epoch so it can be set again. 

259 

260 Only for use in tests and examples. 

261 """ 

262 global _epoch 

263 _epoch = None 

264 

265 

266def set_epoch(epoch): 

267 """ 

268 Set the epoch (origin for dates) for datetime calculations. 

269 

270 The default epoch is :rc:`dates.epoch` (by default 1970-01-01T00:00). 

271 

272 If microsecond accuracy is desired, the date being plotted needs to be 

273 within approximately 70 years of the epoch. Matplotlib internally 

274 represents dates as days since the epoch, so floating point dynamic 

275 range needs to be within a factor of 2^52. 

276 

277 `~.dates.set_epoch` must be called before any dates are converted 

278 (i.e. near the import section) or a RuntimeError will be raised. 

279 

280 See also :doc:`/gallery/ticks/date_precision_and_epochs`. 

281 

282 Parameters 

283 ---------- 

284 epoch : str 

285 valid UTC date parsable by `numpy.datetime64` (do not include 

286 timezone). 

287 

288 """ 

289 global _epoch 

290 if _epoch is not None: 

291 raise RuntimeError('set_epoch must be called before dates plotted.') 

292 _epoch = epoch 

293 

294 

295def get_epoch(): 

296 """ 

297 Get the epoch used by `.dates`. 

298 

299 Returns 

300 ------- 

301 epoch : str 

302 String for the epoch (parsable by `numpy.datetime64`). 

303 """ 

304 global _epoch 

305 

306 _epoch = mpl._val_or_rc(_epoch, 'date.epoch') 

307 return _epoch 

308 

309 

310def _dt64_to_ordinalf(d): 

311 """ 

312 Convert `numpy.datetime64` or an `numpy.ndarray` of those types to 

313 Gregorian date as UTC float relative to the epoch (see `.get_epoch`). 

314 Roundoff is float64 precision. Practically: microseconds for dates 

315 between 290301 BC, 294241 AD, milliseconds for larger dates 

316 (see `numpy.datetime64`). 

317 """ 

318 

319 # the "extra" ensures that we at least allow the dynamic range out to 

320 # seconds. That should get out to +/-2e11 years. 

321 dseconds = d.astype('datetime64[s]') 

322 extra = (d - dseconds).astype('timedelta64[ns]') 

323 t0 = np.datetime64(get_epoch(), 's') 

324 dt = (dseconds - t0).astype(np.float64) 

325 dt += extra.astype(np.float64) / 1.0e9 

326 dt = dt / SEC_PER_DAY 

327 

328 NaT_int = np.datetime64('NaT').astype(np.int64) 

329 d_int = d.astype(np.int64) 

330 dt[d_int == NaT_int] = np.nan 

331 return dt 

332 

333 

334def _from_ordinalf(x, tz=None): 

335 """ 

336 Convert Gregorian float of the date, preserving hours, minutes, 

337 seconds and microseconds. Return value is a `.datetime`. 

338 

339 The input date *x* is a float in ordinal days at UTC, and the output will 

340 be the specified `.datetime` object corresponding to that time in 

341 timezone *tz*, or if *tz* is ``None``, in the timezone specified in 

342 :rc:`timezone`. 

343 """ 

344 

345 tz = _get_tzinfo(tz) 

346 

347 dt = (np.datetime64(get_epoch()) + 

348 np.timedelta64(int(np.round(x * MUSECONDS_PER_DAY)), 'us')) 

349 if dt < np.datetime64('0001-01-01') or dt >= np.datetime64('10000-01-01'): 

350 raise ValueError(f'Date ordinal {x} converts to {dt} (using ' 

351 f'epoch {get_epoch()}), but Matplotlib dates must be ' 

352 'between year 0001 and 9999.') 

353 # convert from datetime64 to datetime: 

354 dt = dt.tolist() 

355 

356 # datetime64 is always UTC: 

357 dt = dt.replace(tzinfo=dateutil.tz.gettz('UTC')) 

358 # but maybe we are working in a different timezone so move. 

359 dt = dt.astimezone(tz) 

360 # fix round off errors 

361 if np.abs(x) > 70 * 365: 

362 # if x is big, round off to nearest twenty microseconds. 

363 # This avoids floating point roundoff error 

364 ms = round(dt.microsecond / 20) * 20 

365 if ms == 1000000: 

366 dt = dt.replace(microsecond=0) + datetime.timedelta(seconds=1) 

367 else: 

368 dt = dt.replace(microsecond=ms) 

369 

370 return dt 

371 

372 

373# a version of _from_ordinalf that can operate on numpy arrays 

374_from_ordinalf_np_vectorized = np.vectorize(_from_ordinalf, otypes="O") 

375# a version of dateutil.parser.parse that can operate on numpy arrays 

376_dateutil_parser_parse_np_vectorized = np.vectorize(dateutil.parser.parse) 

377 

378 

379def datestr2num(d, default=None): 

380 """ 

381 Convert a date string to a datenum using `dateutil.parser.parse`. 

382 

383 Parameters 

384 ---------- 

385 d : str or sequence of str 

386 The dates to convert. 

387 

388 default : datetime.datetime, optional 

389 The default date to use when fields are missing in *d*. 

390 """ 

391 if isinstance(d, str): 

392 dt = dateutil.parser.parse(d, default=default) 

393 return date2num(dt) 

394 else: 

395 if default is not None: 

396 d = [date2num(dateutil.parser.parse(s, default=default)) 

397 for s in d] 

398 return np.asarray(d) 

399 d = np.asarray(d) 

400 if not d.size: 

401 return d 

402 return date2num(_dateutil_parser_parse_np_vectorized(d)) 

403 

404 

405def date2num(d): 

406 """ 

407 Convert datetime objects to Matplotlib dates. 

408 

409 Parameters 

410 ---------- 

411 d : `datetime.datetime` or `numpy.datetime64` or sequences of these 

412 

413 Returns 

414 ------- 

415 float or sequence of floats 

416 Number of days since the epoch. See `.get_epoch` for the 

417 epoch, which can be changed by :rc:`date.epoch` or `.set_epoch`. If 

418 the epoch is "1970-01-01T00:00:00" (default) then noon Jan 1 1970 

419 ("1970-01-01T12:00:00") returns 0.5. 

420 

421 Notes 

422 ----- 

423 The Gregorian calendar is assumed; this is not universal practice. 

424 For details see the module docstring. 

425 """ 

426 # Unpack in case of e.g. Pandas or xarray object 

427 d = cbook._unpack_to_numpy(d) 

428 

429 # make an iterable, but save state to unpack later: 

430 iterable = np.iterable(d) 

431 if not iterable: 

432 d = [d] 

433 

434 masked = np.ma.is_masked(d) 

435 mask = np.ma.getmask(d) 

436 d = np.asarray(d) 

437 

438 # convert to datetime64 arrays, if not already: 

439 if not np.issubdtype(d.dtype, np.datetime64): 

440 # datetime arrays 

441 if not d.size: 

442 # deals with an empty array... 

443 return d 

444 tzi = getattr(d[0], 'tzinfo', None) 

445 if tzi is not None: 

446 # make datetime naive: 

447 d = [dt.astimezone(UTC).replace(tzinfo=None) for dt in d] 

448 d = np.asarray(d) 

449 d = d.astype('datetime64[us]') 

450 

451 d = np.ma.masked_array(d, mask=mask) if masked else d 

452 d = _dt64_to_ordinalf(d) 

453 

454 return d if iterable else d[0] 

455 

456 

457def num2date(x, tz=None): 

458 """ 

459 Convert Matplotlib dates to `~datetime.datetime` objects. 

460 

461 Parameters 

462 ---------- 

463 x : float or sequence of floats 

464 Number of days (fraction part represents hours, minutes, seconds) 

465 since the epoch. See `.get_epoch` for the 

466 epoch, which can be changed by :rc:`date.epoch` or `.set_epoch`. 

467 tz : str or `~datetime.tzinfo`, default: :rc:`timezone` 

468 Timezone of *x*. If a string, *tz* is passed to `dateutil.tz`. 

469 

470 Returns 

471 ------- 

472 `~datetime.datetime` or sequence of `~datetime.datetime` 

473 Dates are returned in timezone *tz*. 

474 

475 If *x* is a sequence, a sequence of `~datetime.datetime` objects will 

476 be returned. 

477 

478 Notes 

479 ----- 

480 The Gregorian calendar is assumed; this is not universal practice. 

481 For details, see the module docstring. 

482 """ 

483 tz = _get_tzinfo(tz) 

484 return _from_ordinalf_np_vectorized(x, tz).tolist() 

485 

486 

487_ordinalf_to_timedelta_np_vectorized = np.vectorize( 

488 lambda x: datetime.timedelta(days=x), otypes="O") 

489 

490 

491def num2timedelta(x): 

492 """ 

493 Convert number of days to a `~datetime.timedelta` object. 

494 

495 If *x* is a sequence, a sequence of `~datetime.timedelta` objects will 

496 be returned. 

497 

498 Parameters 

499 ---------- 

500 x : float, sequence of floats 

501 Number of days. The fraction part represents hours, minutes, seconds. 

502 

503 Returns 

504 ------- 

505 `datetime.timedelta` or list[`datetime.timedelta`] 

506 """ 

507 return _ordinalf_to_timedelta_np_vectorized(x).tolist() 

508 

509 

510def drange(dstart, dend, delta): 

511 """ 

512 Return a sequence of equally spaced Matplotlib dates. 

513 

514 The dates start at *dstart* and reach up to, but not including *dend*. 

515 They are spaced by *delta*. 

516 

517 Parameters 

518 ---------- 

519 dstart, dend : `~datetime.datetime` 

520 The date limits. 

521 delta : `datetime.timedelta` 

522 Spacing of the dates. 

523 

524 Returns 

525 ------- 

526 `numpy.array` 

527 A list floats representing Matplotlib dates. 

528 

529 """ 

530 f1 = date2num(dstart) 

531 f2 = date2num(dend) 

532 step = delta.total_seconds() / SEC_PER_DAY 

533 

534 # calculate the difference between dend and dstart in times of delta 

535 num = int(np.ceil((f2 - f1) / step)) 

536 

537 # calculate end of the interval which will be generated 

538 dinterval_end = dstart + num * delta 

539 

540 # ensure, that an half open interval will be generated [dstart, dend) 

541 if dinterval_end >= dend: 

542 # if the endpoint is greater than or equal to dend, 

543 # just subtract one delta 

544 dinterval_end -= delta 

545 num -= 1 

546 

547 f2 = date2num(dinterval_end) # new float-endpoint 

548 return np.linspace(f1, f2, num + 1) 

549 

550 

551def _wrap_in_tex(text): 

552 p = r'([a-zA-Z]+)' 

553 ret_text = re.sub(p, r'}$\1$\\mathdefault{', text) 

554 

555 # Braces ensure symbols are not spaced like binary operators. 

556 ret_text = ret_text.replace('-', '{-}').replace(':', '{:}') 

557 # To not concatenate space between numbers. 

558 ret_text = ret_text.replace(' ', r'\;') 

559 ret_text = '$\\mathdefault{' + ret_text + '}$' 

560 ret_text = ret_text.replace('$\\mathdefault{}$', '') 

561 return ret_text 

562 

563 

564## date tick locators and formatters ### 

565 

566 

567class DateFormatter(ticker.Formatter): 

568 """ 

569 Format a tick (in days since the epoch) with a 

570 `~datetime.datetime.strftime` format string. 

571 """ 

572 

573 def __init__(self, fmt, tz=None, *, usetex=None): 

574 """ 

575 Parameters 

576 ---------- 

577 fmt : str 

578 `~datetime.datetime.strftime` format string 

579 tz : str or `~datetime.tzinfo`, default: :rc:`timezone` 

580 Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. 

581 usetex : bool, default: :rc:`text.usetex` 

582 To enable/disable the use of TeX's math mode for rendering the 

583 results of the formatter. 

584 """ 

585 self.tz = _get_tzinfo(tz) 

586 self.fmt = fmt 

587 self._usetex = mpl._val_or_rc(usetex, 'text.usetex') 

588 

589 def __call__(self, x, pos=0): 

590 result = num2date(x, self.tz).strftime(self.fmt) 

591 return _wrap_in_tex(result) if self._usetex else result 

592 

593 def set_tzinfo(self, tz): 

594 self.tz = _get_tzinfo(tz) 

595 

596 

597class ConciseDateFormatter(ticker.Formatter): 

598 """ 

599 A `.Formatter` which attempts to figure out the best format to use for the 

600 date, and to make it as compact as possible, but still be complete. This is 

601 most useful when used with the `AutoDateLocator`:: 

602 

603 >>> locator = AutoDateLocator() 

604 >>> formatter = ConciseDateFormatter(locator) 

605 

606 Parameters 

607 ---------- 

608 locator : `.ticker.Locator` 

609 Locator that this axis is using. 

610 

611 tz : str or `~datetime.tzinfo`, default: :rc:`timezone` 

612 Ticks timezone, passed to `.dates.num2date`. 

613 

614 formats : list of 6 strings, optional 

615 Format strings for 6 levels of tick labelling: mostly years, 

616 months, days, hours, minutes, and seconds. Strings use 

617 the same format codes as `~datetime.datetime.strftime`. Default is 

618 ``['%Y', '%b', '%d', '%H:%M', '%H:%M', '%S.%f']`` 

619 

620 zero_formats : list of 6 strings, optional 

621 Format strings for tick labels that are "zeros" for a given tick 

622 level. For instance, if most ticks are months, ticks around 1 Jan 2005 

623 will be labeled "Dec", "2005", "Feb". The default is 

624 ``['', '%Y', '%b', '%b-%d', '%H:%M', '%H:%M']`` 

625 

626 offset_formats : list of 6 strings, optional 

627 Format strings for the 6 levels that is applied to the "offset" 

628 string found on the right side of an x-axis, or top of a y-axis. 

629 Combined with the tick labels this should completely specify the 

630 date. The default is:: 

631 

632 ['', '%Y', '%Y-%b', '%Y-%b-%d', '%Y-%b-%d', '%Y-%b-%d %H:%M'] 

633 

634 show_offset : bool, default: True 

635 Whether to show the offset or not. 

636 

637 usetex : bool, default: :rc:`text.usetex` 

638 To enable/disable the use of TeX's math mode for rendering the results 

639 of the formatter. 

640 

641 Examples 

642 -------- 

643 See :doc:`/gallery/ticks/date_concise_formatter` 

644 

645 .. plot:: 

646 

647 import datetime 

648 import matplotlib.dates as mdates 

649 

650 base = datetime.datetime(2005, 2, 1) 

651 dates = np.array([base + datetime.timedelta(hours=(2 * i)) 

652 for i in range(732)]) 

653 N = len(dates) 

654 np.random.seed(19680801) 

655 y = np.cumsum(np.random.randn(N)) 

656 

657 fig, ax = plt.subplots(constrained_layout=True) 

658 locator = mdates.AutoDateLocator() 

659 formatter = mdates.ConciseDateFormatter(locator) 

660 ax.xaxis.set_major_locator(locator) 

661 ax.xaxis.set_major_formatter(formatter) 

662 

663 ax.plot(dates, y) 

664 ax.set_title('Concise Date Formatter') 

665 

666 """ 

667 

668 def __init__(self, locator, tz=None, formats=None, offset_formats=None, 

669 zero_formats=None, show_offset=True, *, usetex=None): 

670 """ 

671 Autoformat the date labels. The default format is used to form an 

672 initial string, and then redundant elements are removed. 

673 """ 

674 self._locator = locator 

675 self._tz = tz 

676 self.defaultfmt = '%Y' 

677 # there are 6 levels with each level getting a specific format 

678 # 0: mostly years, 1: months, 2: days, 

679 # 3: hours, 4: minutes, 5: seconds 

680 if formats: 

681 if len(formats) != 6: 

682 raise ValueError('formats argument must be a list of ' 

683 '6 format strings (or None)') 

684 self.formats = formats 

685 else: 

686 self.formats = ['%Y', # ticks are mostly years 

687 '%b', # ticks are mostly months 

688 '%d', # ticks are mostly days 

689 '%H:%M', # hrs 

690 '%H:%M', # min 

691 '%S.%f', # secs 

692 ] 

693 # fmt for zeros ticks at this level. These are 

694 # ticks that should be labeled w/ info the level above. 

695 # like 1 Jan can just be labelled "Jan". 02:02:00 can 

696 # just be labeled 02:02. 

697 if zero_formats: 

698 if len(zero_formats) != 6: 

699 raise ValueError('zero_formats argument must be a list of ' 

700 '6 format strings (or None)') 

701 self.zero_formats = zero_formats 

702 elif formats: 

703 # use the users formats for the zero tick formats 

704 self.zero_formats = [''] + self.formats[:-1] 

705 else: 

706 # make the defaults a bit nicer: 

707 self.zero_formats = [''] + self.formats[:-1] 

708 self.zero_formats[3] = '%b-%d' 

709 

710 if offset_formats: 

711 if len(offset_formats) != 6: 

712 raise ValueError('offset_formats argument must be a list of ' 

713 '6 format strings (or None)') 

714 self.offset_formats = offset_formats 

715 else: 

716 self.offset_formats = ['', 

717 '%Y', 

718 '%Y-%b', 

719 '%Y-%b-%d', 

720 '%Y-%b-%d', 

721 '%Y-%b-%d %H:%M'] 

722 self.offset_string = '' 

723 self.show_offset = show_offset 

724 self._usetex = mpl._val_or_rc(usetex, 'text.usetex') 

725 

726 def __call__(self, x, pos=None): 

727 formatter = DateFormatter(self.defaultfmt, self._tz, 

728 usetex=self._usetex) 

729 return formatter(x, pos=pos) 

730 

731 def format_ticks(self, values): 

732 tickdatetime = [num2date(value, tz=self._tz) for value in values] 

733 tickdate = np.array([tdt.timetuple()[:6] for tdt in tickdatetime]) 

734 

735 # basic algorithm: 

736 # 1) only display a part of the date if it changes over the ticks. 

737 # 2) don't display the smaller part of the date if: 

738 # it is always the same or if it is the start of the 

739 # year, month, day etc. 

740 # fmt for most ticks at this level 

741 fmts = self.formats 

742 # format beginnings of days, months, years, etc. 

743 zerofmts = self.zero_formats 

744 # offset fmt are for the offset in the upper left of the 

745 # or lower right of the axis. 

746 offsetfmts = self.offset_formats 

747 show_offset = self.show_offset 

748 

749 # determine the level we will label at: 

750 # mostly 0: years, 1: months, 2: days, 

751 # 3: hours, 4: minutes, 5: seconds, 6: microseconds 

752 for level in range(5, -1, -1): 

753 unique = np.unique(tickdate[:, level]) 

754 if len(unique) > 1: 

755 # if 1 is included in unique, the year is shown in ticks 

756 if level < 2 and np.any(unique == 1): 

757 show_offset = False 

758 break 

759 elif level == 0: 

760 # all tickdate are the same, so only micros might be different 

761 # set to the most precise (6: microseconds doesn't exist...) 

762 level = 5 

763 

764 # level is the basic level we will label at. 

765 # now loop through and decide the actual ticklabels 

766 zerovals = [0, 1, 1, 0, 0, 0, 0] 

767 labels = [''] * len(tickdate) 

768 for nn in range(len(tickdate)): 

769 if level < 5: 

770 if tickdate[nn][level] == zerovals[level]: 

771 fmt = zerofmts[level] 

772 else: 

773 fmt = fmts[level] 

774 else: 

775 # special handling for seconds + microseconds 

776 if (tickdatetime[nn].second == tickdatetime[nn].microsecond 

777 == 0): 

778 fmt = zerofmts[level] 

779 else: 

780 fmt = fmts[level] 

781 labels[nn] = tickdatetime[nn].strftime(fmt) 

782 

783 # special handling of seconds and microseconds: 

784 # strip extra zeros and decimal if possible. 

785 # this is complicated by two factors. 1) we have some level-4 strings 

786 # here (i.e. 03:00, '0.50000', '1.000') 2) we would like to have the 

787 # same number of decimals for each string (i.e. 0.5 and 1.0). 

788 if level >= 5: 

789 trailing_zeros = min( 

790 (len(s) - len(s.rstrip('0')) for s in labels if '.' in s), 

791 default=None) 

792 if trailing_zeros: 

793 for nn in range(len(labels)): 

794 if '.' in labels[nn]: 

795 labels[nn] = labels[nn][:-trailing_zeros].rstrip('.') 

796 

797 if show_offset: 

798 # set the offset string: 

799 self.offset_string = tickdatetime[-1].strftime(offsetfmts[level]) 

800 if self._usetex: 

801 self.offset_string = _wrap_in_tex(self.offset_string) 

802 else: 

803 self.offset_string = '' 

804 

805 if self._usetex: 

806 return [_wrap_in_tex(l) for l in labels] 

807 else: 

808 return labels 

809 

810 def get_offset(self): 

811 return self.offset_string 

812 

813 def format_data_short(self, value): 

814 return num2date(value, tz=self._tz).strftime('%Y-%m-%d %H:%M:%S') 

815 

816 

817class AutoDateFormatter(ticker.Formatter): 

818 """ 

819 A `.Formatter` which attempts to figure out the best format to use. This 

820 is most useful when used with the `AutoDateLocator`. 

821 

822 `.AutoDateFormatter` has a ``.scale`` dictionary that maps tick scales (the 

823 interval in days between one major tick) to format strings; this dictionary 

824 defaults to :: 

825 

826 self.scaled = { 

827 DAYS_PER_YEAR: rcParams['date.autoformatter.year'], 

828 DAYS_PER_MONTH: rcParams['date.autoformatter.month'], 

829 1: rcParams['date.autoformatter.day'], 

830 1 / HOURS_PER_DAY: rcParams['date.autoformatter.hour'], 

831 1 / MINUTES_PER_DAY: rcParams['date.autoformatter.minute'], 

832 1 / SEC_PER_DAY: rcParams['date.autoformatter.second'], 

833 1 / MUSECONDS_PER_DAY: rcParams['date.autoformatter.microsecond'], 

834 } 

835 

836 The formatter uses the format string corresponding to the lowest key in 

837 the dictionary that is greater or equal to the current scale. Dictionary 

838 entries can be customized:: 

839 

840 locator = AutoDateLocator() 

841 formatter = AutoDateFormatter(locator) 

842 formatter.scaled[1/(24*60)] = '%M:%S' # only show min and sec 

843 

844 Custom callables can also be used instead of format strings. The following 

845 example shows how to use a custom format function to strip trailing zeros 

846 from decimal seconds and adds the date to the first ticklabel:: 

847 

848 def my_format_function(x, pos=None): 

849 x = matplotlib.dates.num2date(x) 

850 if pos == 0: 

851 fmt = '%D %H:%M:%S.%f' 

852 else: 

853 fmt = '%H:%M:%S.%f' 

854 label = x.strftime(fmt) 

855 label = label.rstrip("0") 

856 label = label.rstrip(".") 

857 return label 

858 

859 formatter.scaled[1/(24*60)] = my_format_function 

860 """ 

861 

862 # This can be improved by providing some user-level direction on 

863 # how to choose the best format (precedence, etc.). 

864 

865 # Perhaps a 'struct' that has a field for each time-type where a 

866 # zero would indicate "don't show" and a number would indicate 

867 # "show" with some sort of priority. Same priorities could mean 

868 # show all with the same priority. 

869 

870 # Or more simply, perhaps just a format string for each 

871 # possibility... 

872 

873 def __init__(self, locator, tz=None, defaultfmt='%Y-%m-%d', *, 

874 usetex=None): 

875 """ 

876 Autoformat the date labels. 

877 

878 Parameters 

879 ---------- 

880 locator : `.ticker.Locator` 

881 Locator that this axis is using. 

882 

883 tz : str or `~datetime.tzinfo`, default: :rc:`timezone` 

884 Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. 

885 

886 defaultfmt : str 

887 The default format to use if none of the values in ``self.scaled`` 

888 are greater than the unit returned by ``locator._get_unit()``. 

889 

890 usetex : bool, default: :rc:`text.usetex` 

891 To enable/disable the use of TeX's math mode for rendering the 

892 results of the formatter. If any entries in ``self.scaled`` are set 

893 as functions, then it is up to the customized function to enable or 

894 disable TeX's math mode itself. 

895 """ 

896 self._locator = locator 

897 self._tz = tz 

898 self.defaultfmt = defaultfmt 

899 self._formatter = DateFormatter(self.defaultfmt, tz) 

900 rcParams = mpl.rcParams 

901 self._usetex = mpl._val_or_rc(usetex, 'text.usetex') 

902 self.scaled = { 

903 DAYS_PER_YEAR: rcParams['date.autoformatter.year'], 

904 DAYS_PER_MONTH: rcParams['date.autoformatter.month'], 

905 1: rcParams['date.autoformatter.day'], 

906 1 / HOURS_PER_DAY: rcParams['date.autoformatter.hour'], 

907 1 / MINUTES_PER_DAY: rcParams['date.autoformatter.minute'], 

908 1 / SEC_PER_DAY: rcParams['date.autoformatter.second'], 

909 1 / MUSECONDS_PER_DAY: rcParams['date.autoformatter.microsecond'] 

910 } 

911 

912 def _set_locator(self, locator): 

913 self._locator = locator 

914 

915 def __call__(self, x, pos=None): 

916 try: 

917 locator_unit_scale = float(self._locator._get_unit()) 

918 except AttributeError: 

919 locator_unit_scale = 1 

920 # Pick the first scale which is greater than the locator unit. 

921 fmt = next((fmt for scale, fmt in sorted(self.scaled.items()) 

922 if scale >= locator_unit_scale), 

923 self.defaultfmt) 

924 

925 if isinstance(fmt, str): 

926 self._formatter = DateFormatter(fmt, self._tz, usetex=self._usetex) 

927 result = self._formatter(x, pos) 

928 elif callable(fmt): 

929 result = fmt(x, pos) 

930 else: 

931 raise TypeError(f'Unexpected type passed to {self!r}.') 

932 

933 return result 

934 

935 

936class rrulewrapper: 

937 """ 

938 A simple wrapper around a `dateutil.rrule` allowing flexible 

939 date tick specifications. 

940 """ 

941 def __init__(self, freq, tzinfo=None, **kwargs): 

942 """ 

943 Parameters 

944 ---------- 

945 freq : {YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, SECONDLY} 

946 Tick frequency. These constants are defined in `dateutil.rrule`, 

947 but they are accessible from `matplotlib.dates` as well. 

948 tzinfo : `datetime.tzinfo`, optional 

949 Time zone information. The default is None. 

950 **kwargs 

951 Additional keyword arguments are passed to the `dateutil.rrule`. 

952 """ 

953 kwargs['freq'] = freq 

954 self._base_tzinfo = tzinfo 

955 

956 self._update_rrule(**kwargs) 

957 

958 def set(self, **kwargs): 

959 """Set parameters for an existing wrapper.""" 

960 self._construct.update(kwargs) 

961 

962 self._update_rrule(**self._construct) 

963 

964 def _update_rrule(self, **kwargs): 

965 tzinfo = self._base_tzinfo 

966 

967 # rrule does not play nicely with timezones - especially pytz time 

968 # zones, it's best to use naive zones and attach timezones once the 

969 # datetimes are returned 

970 if 'dtstart' in kwargs: 

971 dtstart = kwargs['dtstart'] 

972 if dtstart.tzinfo is not None: 

973 if tzinfo is None: 

974 tzinfo = dtstart.tzinfo 

975 else: 

976 dtstart = dtstart.astimezone(tzinfo) 

977 

978 kwargs['dtstart'] = dtstart.replace(tzinfo=None) 

979 

980 if 'until' in kwargs: 

981 until = kwargs['until'] 

982 if until.tzinfo is not None: 

983 if tzinfo is not None: 

984 until = until.astimezone(tzinfo) 

985 else: 

986 raise ValueError('until cannot be aware if dtstart ' 

987 'is naive and tzinfo is None') 

988 

989 kwargs['until'] = until.replace(tzinfo=None) 

990 

991 self._construct = kwargs.copy() 

992 self._tzinfo = tzinfo 

993 self._rrule = rrule(**self._construct) 

994 

995 def _attach_tzinfo(self, dt, tzinfo): 

996 # pytz zones are attached by "localizing" the datetime 

997 if hasattr(tzinfo, 'localize'): 

998 return tzinfo.localize(dt, is_dst=True) 

999 

1000 return dt.replace(tzinfo=tzinfo) 

1001 

1002 def _aware_return_wrapper(self, f, returns_list=False): 

1003 """Decorator function that allows rrule methods to handle tzinfo.""" 

1004 # This is only necessary if we're actually attaching a tzinfo 

1005 if self._tzinfo is None: 

1006 return f 

1007 

1008 # All datetime arguments must be naive. If they are not naive, they are 

1009 # converted to the _tzinfo zone before dropping the zone. 

1010 def normalize_arg(arg): 

1011 if isinstance(arg, datetime.datetime) and arg.tzinfo is not None: 

1012 if arg.tzinfo is not self._tzinfo: 

1013 arg = arg.astimezone(self._tzinfo) 

1014 

1015 return arg.replace(tzinfo=None) 

1016 

1017 return arg 

1018 

1019 def normalize_args(args, kwargs): 

1020 args = tuple(normalize_arg(arg) for arg in args) 

1021 kwargs = {kw: normalize_arg(arg) for kw, arg in kwargs.items()} 

1022 

1023 return args, kwargs 

1024 

1025 # There are two kinds of functions we care about - ones that return 

1026 # dates and ones that return lists of dates. 

1027 if not returns_list: 

1028 def inner_func(*args, **kwargs): 

1029 args, kwargs = normalize_args(args, kwargs) 

1030 dt = f(*args, **kwargs) 

1031 return self._attach_tzinfo(dt, self._tzinfo) 

1032 else: 

1033 def inner_func(*args, **kwargs): 

1034 args, kwargs = normalize_args(args, kwargs) 

1035 dts = f(*args, **kwargs) 

1036 return [self._attach_tzinfo(dt, self._tzinfo) for dt in dts] 

1037 

1038 return functools.wraps(f)(inner_func) 

1039 

1040 def __getattr__(self, name): 

1041 if name in self.__dict__: 

1042 return self.__dict__[name] 

1043 

1044 f = getattr(self._rrule, name) 

1045 

1046 if name in {'after', 'before'}: 

1047 return self._aware_return_wrapper(f) 

1048 elif name in {'xafter', 'xbefore', 'between'}: 

1049 return self._aware_return_wrapper(f, returns_list=True) 

1050 else: 

1051 return f 

1052 

1053 def __setstate__(self, state): 

1054 self.__dict__.update(state) 

1055 

1056 

1057class DateLocator(ticker.Locator): 

1058 """ 

1059 Determines the tick locations when plotting dates. 

1060 

1061 This class is subclassed by other Locators and 

1062 is not meant to be used on its own. 

1063 """ 

1064 hms0d = {'byhour': 0, 'byminute': 0, 'bysecond': 0} 

1065 

1066 def __init__(self, tz=None): 

1067 """ 

1068 Parameters 

1069 ---------- 

1070 tz : str or `~datetime.tzinfo`, default: :rc:`timezone` 

1071 Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. 

1072 """ 

1073 self.tz = _get_tzinfo(tz) 

1074 

1075 def set_tzinfo(self, tz): 

1076 """ 

1077 Set timezone info. 

1078 

1079 Parameters 

1080 ---------- 

1081 tz : str or `~datetime.tzinfo`, default: :rc:`timezone` 

1082 Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. 

1083 """ 

1084 self.tz = _get_tzinfo(tz) 

1085 

1086 def datalim_to_dt(self): 

1087 """Convert axis data interval to datetime objects.""" 

1088 dmin, dmax = self.axis.get_data_interval() 

1089 if dmin > dmax: 

1090 dmin, dmax = dmax, dmin 

1091 

1092 return num2date(dmin, self.tz), num2date(dmax, self.tz) 

1093 

1094 def viewlim_to_dt(self): 

1095 """Convert the view interval to datetime objects.""" 

1096 vmin, vmax = self.axis.get_view_interval() 

1097 if vmin > vmax: 

1098 vmin, vmax = vmax, vmin 

1099 return num2date(vmin, self.tz), num2date(vmax, self.tz) 

1100 

1101 def _get_unit(self): 

1102 """ 

1103 Return how many days a unit of the locator is; used for 

1104 intelligent autoscaling. 

1105 """ 

1106 return 1 

1107 

1108 def _get_interval(self): 

1109 """ 

1110 Return the number of units for each tick. 

1111 """ 

1112 return 1 

1113 

1114 def nonsingular(self, vmin, vmax): 

1115 """ 

1116 Given the proposed upper and lower extent, adjust the range 

1117 if it is too close to being singular (i.e. a range of ~0). 

1118 """ 

1119 if not np.isfinite(vmin) or not np.isfinite(vmax): 

1120 # Except if there is no data, then use 1970 as default. 

1121 return (date2num(datetime.date(1970, 1, 1)), 

1122 date2num(datetime.date(1970, 1, 2))) 

1123 if vmax < vmin: 

1124 vmin, vmax = vmax, vmin 

1125 unit = self._get_unit() 

1126 interval = self._get_interval() 

1127 if abs(vmax - vmin) < 1e-6: 

1128 vmin -= 2 * unit * interval 

1129 vmax += 2 * unit * interval 

1130 return vmin, vmax 

1131 

1132 

1133class RRuleLocator(DateLocator): 

1134 # use the dateutil rrule instance 

1135 

1136 def __init__(self, o, tz=None): 

1137 super().__init__(tz) 

1138 self.rule = o 

1139 

1140 def __call__(self): 

1141 # if no data have been set, this will tank with a ValueError 

1142 try: 

1143 dmin, dmax = self.viewlim_to_dt() 

1144 except ValueError: 

1145 return [] 

1146 

1147 return self.tick_values(dmin, dmax) 

1148 

1149 def tick_values(self, vmin, vmax): 

1150 start, stop = self._create_rrule(vmin, vmax) 

1151 dates = self.rule.between(start, stop, True) 

1152 if len(dates) == 0: 

1153 return date2num([vmin, vmax]) 

1154 return self.raise_if_exceeds(date2num(dates)) 

1155 

1156 def _create_rrule(self, vmin, vmax): 

1157 # set appropriate rrule dtstart and until and return 

1158 # start and end 

1159 delta = relativedelta(vmax, vmin) 

1160 

1161 # We need to cap at the endpoints of valid datetime 

1162 try: 

1163 start = vmin - delta 

1164 except (ValueError, OverflowError): 

1165 # cap 

1166 start = datetime.datetime(1, 1, 1, 0, 0, 0, 

1167 tzinfo=datetime.timezone.utc) 

1168 

1169 try: 

1170 stop = vmax + delta 

1171 except (ValueError, OverflowError): 

1172 # cap 

1173 stop = datetime.datetime(9999, 12, 31, 23, 59, 59, 

1174 tzinfo=datetime.timezone.utc) 

1175 

1176 self.rule.set(dtstart=start, until=stop) 

1177 

1178 return vmin, vmax 

1179 

1180 def _get_unit(self): 

1181 # docstring inherited 

1182 freq = self.rule._rrule._freq 

1183 return self.get_unit_generic(freq) 

1184 

1185 @staticmethod 

1186 def get_unit_generic(freq): 

1187 if freq == YEARLY: 

1188 return DAYS_PER_YEAR 

1189 elif freq == MONTHLY: 

1190 return DAYS_PER_MONTH 

1191 elif freq == WEEKLY: 

1192 return DAYS_PER_WEEK 

1193 elif freq == DAILY: 

1194 return 1.0 

1195 elif freq == HOURLY: 

1196 return 1.0 / HOURS_PER_DAY 

1197 elif freq == MINUTELY: 

1198 return 1.0 / MINUTES_PER_DAY 

1199 elif freq == SECONDLY: 

1200 return 1.0 / SEC_PER_DAY 

1201 else: 

1202 # error 

1203 return -1 # or should this just return '1'? 

1204 

1205 def _get_interval(self): 

1206 return self.rule._rrule._interval 

1207 

1208 

1209class AutoDateLocator(DateLocator): 

1210 """ 

1211 On autoscale, this class picks the best `DateLocator` to set the view 

1212 limits and the tick locations. 

1213 

1214 Attributes 

1215 ---------- 

1216 intervald : dict 

1217 

1218 Mapping of tick frequencies to multiples allowed for that ticking. 

1219 The default is :: 

1220 

1221 self.intervald = { 

1222 YEARLY : [1, 2, 4, 5, 10, 20, 40, 50, 100, 200, 400, 500, 

1223 1000, 2000, 4000, 5000, 10000], 

1224 MONTHLY : [1, 2, 3, 4, 6], 

1225 DAILY : [1, 2, 3, 7, 14, 21], 

1226 HOURLY : [1, 2, 3, 4, 6, 12], 

1227 MINUTELY: [1, 5, 10, 15, 30], 

1228 SECONDLY: [1, 5, 10, 15, 30], 

1229 MICROSECONDLY: [1, 2, 5, 10, 20, 50, 100, 200, 500, 

1230 1000, 2000, 5000, 10000, 20000, 50000, 

1231 100000, 200000, 500000, 1000000], 

1232 } 

1233 

1234 where the keys are defined in `dateutil.rrule`. 

1235 

1236 The interval is used to specify multiples that are appropriate for 

1237 the frequency of ticking. For instance, every 7 days is sensible 

1238 for daily ticks, but for minutes/seconds, 15 or 30 make sense. 

1239 

1240 When customizing, you should only modify the values for the existing 

1241 keys. You should not add or delete entries. 

1242 

1243 Example for forcing ticks every 3 hours:: 

1244 

1245 locator = AutoDateLocator() 

1246 locator.intervald[HOURLY] = [3] # only show every 3 hours 

1247 """ 

1248 

1249 def __init__(self, tz=None, minticks=5, maxticks=None, 

1250 interval_multiples=True): 

1251 """ 

1252 Parameters 

1253 ---------- 

1254 tz : str or `~datetime.tzinfo`, default: :rc:`timezone` 

1255 Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. 

1256 minticks : int 

1257 The minimum number of ticks desired; controls whether ticks occur 

1258 yearly, monthly, etc. 

1259 maxticks : int 

1260 The maximum number of ticks desired; controls the interval between 

1261 ticks (ticking every other, every 3, etc.). For fine-grained 

1262 control, this can be a dictionary mapping individual rrule 

1263 frequency constants (YEARLY, MONTHLY, etc.) to their own maximum 

1264 number of ticks. This can be used to keep the number of ticks 

1265 appropriate to the format chosen in `AutoDateFormatter`. Any 

1266 frequency not specified in this dictionary is given a default 

1267 value. 

1268 interval_multiples : bool, default: True 

1269 Whether ticks should be chosen to be multiple of the interval, 

1270 locking them to 'nicer' locations. For example, this will force 

1271 the ticks to be at hours 0, 6, 12, 18 when hourly ticking is done 

1272 at 6 hour intervals. 

1273 """ 

1274 super().__init__(tz=tz) 

1275 self._freq = YEARLY 

1276 self._freqs = [YEARLY, MONTHLY, DAILY, HOURLY, MINUTELY, 

1277 SECONDLY, MICROSECONDLY] 

1278 self.minticks = minticks 

1279 

1280 self.maxticks = {YEARLY: 11, MONTHLY: 12, DAILY: 11, HOURLY: 12, 

1281 MINUTELY: 11, SECONDLY: 11, MICROSECONDLY: 8} 

1282 if maxticks is not None: 

1283 try: 

1284 self.maxticks.update(maxticks) 

1285 except TypeError: 

1286 # Assume we were given an integer. Use this as the maximum 

1287 # number of ticks for every frequency and create a 

1288 # dictionary for this 

1289 self.maxticks = dict.fromkeys(self._freqs, maxticks) 

1290 self.interval_multiples = interval_multiples 

1291 self.intervald = { 

1292 YEARLY: [1, 2, 4, 5, 10, 20, 40, 50, 100, 200, 400, 500, 

1293 1000, 2000, 4000, 5000, 10000], 

1294 MONTHLY: [1, 2, 3, 4, 6], 

1295 DAILY: [1, 2, 3, 7, 14, 21], 

1296 HOURLY: [1, 2, 3, 4, 6, 12], 

1297 MINUTELY: [1, 5, 10, 15, 30], 

1298 SECONDLY: [1, 5, 10, 15, 30], 

1299 MICROSECONDLY: [1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 

1300 5000, 10000, 20000, 50000, 100000, 200000, 500000, 

1301 1000000], 

1302 } 

1303 if interval_multiples: 

1304 # Swap "3" for "4" in the DAILY list; If we use 3 we get bad 

1305 # tick loc for months w/ 31 days: 1, 4, ..., 28, 31, 1 

1306 # If we use 4 then we get: 1, 5, ... 25, 29, 1 

1307 self.intervald[DAILY] = [1, 2, 4, 7, 14] 

1308 

1309 self._byranges = [None, range(1, 13), range(1, 32), 

1310 range(0, 24), range(0, 60), range(0, 60), None] 

1311 

1312 def __call__(self): 

1313 # docstring inherited 

1314 dmin, dmax = self.viewlim_to_dt() 

1315 locator = self.get_locator(dmin, dmax) 

1316 return locator() 

1317 

1318 def tick_values(self, vmin, vmax): 

1319 return self.get_locator(vmin, vmax).tick_values(vmin, vmax) 

1320 

1321 def nonsingular(self, vmin, vmax): 

1322 # whatever is thrown at us, we can scale the unit. 

1323 # But default nonsingular date plots at an ~4 year period. 

1324 if not np.isfinite(vmin) or not np.isfinite(vmax): 

1325 # Except if there is no data, then use 1970 as default. 

1326 return (date2num(datetime.date(1970, 1, 1)), 

1327 date2num(datetime.date(1970, 1, 2))) 

1328 if vmax < vmin: 

1329 vmin, vmax = vmax, vmin 

1330 if vmin == vmax: 

1331 vmin = vmin - DAYS_PER_YEAR * 2 

1332 vmax = vmax + DAYS_PER_YEAR * 2 

1333 return vmin, vmax 

1334 

1335 def _get_unit(self): 

1336 if self._freq in [MICROSECONDLY]: 

1337 return 1. / MUSECONDS_PER_DAY 

1338 else: 

1339 return RRuleLocator.get_unit_generic(self._freq) 

1340 

1341 def get_locator(self, dmin, dmax): 

1342 """Pick the best locator based on a distance.""" 

1343 delta = relativedelta(dmax, dmin) 

1344 tdelta = dmax - dmin 

1345 

1346 # take absolute difference 

1347 if dmin > dmax: 

1348 delta = -delta 

1349 tdelta = -tdelta 

1350 # The following uses a mix of calls to relativedelta and timedelta 

1351 # methods because there is incomplete overlap in the functionality of 

1352 # these similar functions, and it's best to avoid doing our own math 

1353 # whenever possible. 

1354 numYears = float(delta.years) 

1355 numMonths = numYears * MONTHS_PER_YEAR + delta.months 

1356 numDays = tdelta.days # Avoids estimates of days/month, days/year. 

1357 numHours = numDays * HOURS_PER_DAY + delta.hours 

1358 numMinutes = numHours * MIN_PER_HOUR + delta.minutes 

1359 numSeconds = np.floor(tdelta.total_seconds()) 

1360 numMicroseconds = np.floor(tdelta.total_seconds() * 1e6) 

1361 

1362 nums = [numYears, numMonths, numDays, numHours, numMinutes, 

1363 numSeconds, numMicroseconds] 

1364 

1365 use_rrule_locator = [True] * 6 + [False] 

1366 

1367 # Default setting of bymonth, etc. to pass to rrule 

1368 # [unused (for year), bymonth, bymonthday, byhour, byminute, 

1369 # bysecond, unused (for microseconds)] 

1370 byranges = [None, 1, 1, 0, 0, 0, None] 

1371 

1372 # Loop over all the frequencies and try to find one that gives at 

1373 # least a minticks tick positions. Once this is found, look for 

1374 # an interval from a list specific to that frequency that gives no 

1375 # more than maxticks tick positions. Also, set up some ranges 

1376 # (bymonth, etc.) as appropriate to be passed to rrulewrapper. 

1377 for i, (freq, num) in enumerate(zip(self._freqs, nums)): 

1378 # If this particular frequency doesn't give enough ticks, continue 

1379 if num < self.minticks: 

1380 # Since we're not using this particular frequency, set 

1381 # the corresponding by_ to None so the rrule can act as 

1382 # appropriate 

1383 byranges[i] = None 

1384 continue 

1385 

1386 # Find the first available interval that doesn't give too many 

1387 # ticks 

1388 for interval in self.intervald[freq]: 

1389 if num <= interval * (self.maxticks[freq] - 1): 

1390 break 

1391 else: 

1392 if not (self.interval_multiples and freq == DAILY): 

1393 _api.warn_external( 

1394 f"AutoDateLocator was unable to pick an appropriate " 

1395 f"interval for this date range. It may be necessary " 

1396 f"to add an interval value to the AutoDateLocator's " 

1397 f"intervald dictionary. Defaulting to {interval}.") 

1398 

1399 # Set some parameters as appropriate 

1400 self._freq = freq 

1401 

1402 if self._byranges[i] and self.interval_multiples: 

1403 byranges[i] = self._byranges[i][::interval] 

1404 if i in (DAILY, WEEKLY): 

1405 if interval == 14: 

1406 # just make first and 15th. Avoids 30th. 

1407 byranges[i] = [1, 15] 

1408 elif interval == 7: 

1409 byranges[i] = [1, 8, 15, 22] 

1410 

1411 interval = 1 

1412 else: 

1413 byranges[i] = self._byranges[i] 

1414 break 

1415 else: 

1416 interval = 1 

1417 

1418 if (freq == YEARLY) and self.interval_multiples: 

1419 locator = YearLocator(interval, tz=self.tz) 

1420 elif use_rrule_locator[i]: 

1421 _, bymonth, bymonthday, byhour, byminute, bysecond, _ = byranges 

1422 rrule = rrulewrapper(self._freq, interval=interval, 

1423 dtstart=dmin, until=dmax, 

1424 bymonth=bymonth, bymonthday=bymonthday, 

1425 byhour=byhour, byminute=byminute, 

1426 bysecond=bysecond) 

1427 

1428 locator = RRuleLocator(rrule, tz=self.tz) 

1429 else: 

1430 locator = MicrosecondLocator(interval, tz=self.tz) 

1431 if date2num(dmin) > 70 * 365 and interval < 1000: 

1432 _api.warn_external( 

1433 'Plotting microsecond time intervals for dates far from ' 

1434 f'the epoch (time origin: {get_epoch()}) is not well-' 

1435 'supported. See matplotlib.dates.set_epoch to change the ' 

1436 'epoch.') 

1437 

1438 locator.set_axis(self.axis) 

1439 return locator 

1440 

1441 

1442class YearLocator(RRuleLocator): 

1443 """ 

1444 Make ticks on a given day of each year that is a multiple of base. 

1445 

1446 Examples:: 

1447 

1448 # Tick every year on Jan 1st 

1449 locator = YearLocator() 

1450 

1451 # Tick every 5 years on July 4th 

1452 locator = YearLocator(5, month=7, day=4) 

1453 """ 

1454 def __init__(self, base=1, month=1, day=1, tz=None): 

1455 """ 

1456 Parameters 

1457 ---------- 

1458 base : int, default: 1 

1459 Mark ticks every *base* years. 

1460 month : int, default: 1 

1461 The month on which to place the ticks, starting from 1. Default is 

1462 January. 

1463 day : int, default: 1 

1464 The day on which to place the ticks. 

1465 tz : str or `~datetime.tzinfo`, default: :rc:`timezone` 

1466 Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. 

1467 """ 

1468 rule = rrulewrapper(YEARLY, interval=base, bymonth=month, 

1469 bymonthday=day, **self.hms0d) 

1470 super().__init__(rule, tz=tz) 

1471 self.base = ticker._Edge_integer(base, 0) 

1472 

1473 def _create_rrule(self, vmin, vmax): 

1474 # 'start' needs to be a multiple of the interval to create ticks on 

1475 # interval multiples when the tick frequency is YEARLY 

1476 ymin = max(self.base.le(vmin.year) * self.base.step, 1) 

1477 ymax = min(self.base.ge(vmax.year) * self.base.step, 9999) 

1478 

1479 c = self.rule._construct 

1480 replace = {'year': ymin, 

1481 'month': c.get('bymonth', 1), 

1482 'day': c.get('bymonthday', 1), 

1483 'hour': 0, 'minute': 0, 'second': 0} 

1484 

1485 start = vmin.replace(**replace) 

1486 stop = start.replace(year=ymax) 

1487 self.rule.set(dtstart=start, until=stop) 

1488 

1489 return start, stop 

1490 

1491 

1492class MonthLocator(RRuleLocator): 

1493 """ 

1494 Make ticks on occurrences of each month, e.g., 1, 3, 12. 

1495 """ 

1496 def __init__(self, bymonth=None, bymonthday=1, interval=1, tz=None): 

1497 """ 

1498 Parameters 

1499 ---------- 

1500 bymonth : int or list of int, default: all months 

1501 Ticks will be placed on every month in *bymonth*. Default is 

1502 ``range(1, 13)``, i.e. every month. 

1503 bymonthday : int, default: 1 

1504 The day on which to place the ticks. 

1505 interval : int, default: 1 

1506 The interval between each iteration. For example, if 

1507 ``interval=2``, mark every second occurrence. 

1508 tz : str or `~datetime.tzinfo`, default: :rc:`timezone` 

1509 Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. 

1510 """ 

1511 if bymonth is None: 

1512 bymonth = range(1, 13) 

1513 

1514 rule = rrulewrapper(MONTHLY, bymonth=bymonth, bymonthday=bymonthday, 

1515 interval=interval, **self.hms0d) 

1516 super().__init__(rule, tz=tz) 

1517 

1518 

1519class WeekdayLocator(RRuleLocator): 

1520 """ 

1521 Make ticks on occurrences of each weekday. 

1522 """ 

1523 

1524 def __init__(self, byweekday=1, interval=1, tz=None): 

1525 """ 

1526 Parameters 

1527 ---------- 

1528 byweekday : int or list of int, default: all days 

1529 Ticks will be placed on every weekday in *byweekday*. Default is 

1530 every day. 

1531 

1532 Elements of *byweekday* must be one of MO, TU, WE, TH, FR, SA, 

1533 SU, the constants from :mod:`dateutil.rrule`, which have been 

1534 imported into the :mod:`matplotlib.dates` namespace. 

1535 interval : int, default: 1 

1536 The interval between each iteration. For example, if 

1537 ``interval=2``, mark every second occurrence. 

1538 tz : str or `~datetime.tzinfo`, default: :rc:`timezone` 

1539 Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. 

1540 """ 

1541 rule = rrulewrapper(DAILY, byweekday=byweekday, 

1542 interval=interval, **self.hms0d) 

1543 super().__init__(rule, tz=tz) 

1544 

1545 

1546class DayLocator(RRuleLocator): 

1547 """ 

1548 Make ticks on occurrences of each day of the month. For example, 

1549 1, 15, 30. 

1550 """ 

1551 def __init__(self, bymonthday=None, interval=1, tz=None): 

1552 """ 

1553 Parameters 

1554 ---------- 

1555 bymonthday : int or list of int, default: all days 

1556 Ticks will be placed on every day in *bymonthday*. Default is 

1557 ``bymonthday=range(1, 32)``, i.e., every day of the month. 

1558 interval : int, default: 1 

1559 The interval between each iteration. For example, if 

1560 ``interval=2``, mark every second occurrence. 

1561 tz : str or `~datetime.tzinfo`, default: :rc:`timezone` 

1562 Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. 

1563 """ 

1564 if interval != int(interval) or interval < 1: 

1565 raise ValueError("interval must be an integer greater than 0") 

1566 if bymonthday is None: 

1567 bymonthday = range(1, 32) 

1568 

1569 rule = rrulewrapper(DAILY, bymonthday=bymonthday, 

1570 interval=interval, **self.hms0d) 

1571 super().__init__(rule, tz=tz) 

1572 

1573 

1574class HourLocator(RRuleLocator): 

1575 """ 

1576 Make ticks on occurrences of each hour. 

1577 """ 

1578 def __init__(self, byhour=None, interval=1, tz=None): 

1579 """ 

1580 Parameters 

1581 ---------- 

1582 byhour : int or list of int, default: all hours 

1583 Ticks will be placed on every hour in *byhour*. Default is 

1584 ``byhour=range(24)``, i.e., every hour. 

1585 interval : int, default: 1 

1586 The interval between each iteration. For example, if 

1587 ``interval=2``, mark every second occurrence. 

1588 tz : str or `~datetime.tzinfo`, default: :rc:`timezone` 

1589 Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. 

1590 """ 

1591 if byhour is None: 

1592 byhour = range(24) 

1593 

1594 rule = rrulewrapper(HOURLY, byhour=byhour, interval=interval, 

1595 byminute=0, bysecond=0) 

1596 super().__init__(rule, tz=tz) 

1597 

1598 

1599class MinuteLocator(RRuleLocator): 

1600 """ 

1601 Make ticks on occurrences of each minute. 

1602 """ 

1603 def __init__(self, byminute=None, interval=1, tz=None): 

1604 """ 

1605 Parameters 

1606 ---------- 

1607 byminute : int or list of int, default: all minutes 

1608 Ticks will be placed on every minute in *byminute*. Default is 

1609 ``byminute=range(60)``, i.e., every minute. 

1610 interval : int, default: 1 

1611 The interval between each iteration. For example, if 

1612 ``interval=2``, mark every second occurrence. 

1613 tz : str or `~datetime.tzinfo`, default: :rc:`timezone` 

1614 Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. 

1615 """ 

1616 if byminute is None: 

1617 byminute = range(60) 

1618 

1619 rule = rrulewrapper(MINUTELY, byminute=byminute, interval=interval, 

1620 bysecond=0) 

1621 super().__init__(rule, tz=tz) 

1622 

1623 

1624class SecondLocator(RRuleLocator): 

1625 """ 

1626 Make ticks on occurrences of each second. 

1627 """ 

1628 def __init__(self, bysecond=None, interval=1, tz=None): 

1629 """ 

1630 Parameters 

1631 ---------- 

1632 bysecond : int or list of int, default: all seconds 

1633 Ticks will be placed on every second in *bysecond*. Default is 

1634 ``bysecond = range(60)``, i.e., every second. 

1635 interval : int, default: 1 

1636 The interval between each iteration. For example, if 

1637 ``interval=2``, mark every second occurrence. 

1638 tz : str or `~datetime.tzinfo`, default: :rc:`timezone` 

1639 Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. 

1640 """ 

1641 if bysecond is None: 

1642 bysecond = range(60) 

1643 

1644 rule = rrulewrapper(SECONDLY, bysecond=bysecond, interval=interval) 

1645 super().__init__(rule, tz=tz) 

1646 

1647 

1648class MicrosecondLocator(DateLocator): 

1649 """ 

1650 Make ticks on regular intervals of one or more microsecond(s). 

1651 

1652 .. note:: 

1653 

1654 By default, Matplotlib uses a floating point representation of time in 

1655 days since the epoch, so plotting data with 

1656 microsecond time resolution does not work well for 

1657 dates that are far (about 70 years) from the epoch (check with 

1658 `~.dates.get_epoch`). 

1659 

1660 If you want sub-microsecond resolution time plots, it is strongly 

1661 recommended to use floating point seconds, not datetime-like 

1662 time representation. 

1663 

1664 If you really must use datetime.datetime() or similar and still 

1665 need microsecond precision, change the time origin via 

1666 `.dates.set_epoch` to something closer to the dates being plotted. 

1667 See :doc:`/gallery/ticks/date_precision_and_epochs`. 

1668 

1669 """ 

1670 def __init__(self, interval=1, tz=None): 

1671 """ 

1672 Parameters 

1673 ---------- 

1674 interval : int, default: 1 

1675 The interval between each iteration. For example, if 

1676 ``interval=2``, mark every second occurrence. 

1677 tz : str or `~datetime.tzinfo`, default: :rc:`timezone` 

1678 Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. 

1679 """ 

1680 super().__init__(tz=tz) 

1681 self._interval = interval 

1682 self._wrapped_locator = ticker.MultipleLocator(interval) 

1683 

1684 def set_axis(self, axis): 

1685 self._wrapped_locator.set_axis(axis) 

1686 return super().set_axis(axis) 

1687 

1688 def __call__(self): 

1689 # if no data have been set, this will tank with a ValueError 

1690 try: 

1691 dmin, dmax = self.viewlim_to_dt() 

1692 except ValueError: 

1693 return [] 

1694 

1695 return self.tick_values(dmin, dmax) 

1696 

1697 def tick_values(self, vmin, vmax): 

1698 nmin, nmax = date2num((vmin, vmax)) 

1699 t0 = np.floor(nmin) 

1700 nmax = nmax - t0 

1701 nmin = nmin - t0 

1702 nmin *= MUSECONDS_PER_DAY 

1703 nmax *= MUSECONDS_PER_DAY 

1704 

1705 ticks = self._wrapped_locator.tick_values(nmin, nmax) 

1706 

1707 ticks = ticks / MUSECONDS_PER_DAY + t0 

1708 return ticks 

1709 

1710 def _get_unit(self): 

1711 # docstring inherited 

1712 return 1. / MUSECONDS_PER_DAY 

1713 

1714 def _get_interval(self): 

1715 # docstring inherited 

1716 return self._interval 

1717 

1718 

1719class DateConverter(units.ConversionInterface): 

1720 """ 

1721 Converter for `datetime.date` and `datetime.datetime` data, or for 

1722 date/time data represented as it would be converted by `date2num`. 

1723 

1724 The 'unit' tag for such data is None or a `~datetime.tzinfo` instance. 

1725 """ 

1726 

1727 def __init__(self, *, interval_multiples=True): 

1728 self._interval_multiples = interval_multiples 

1729 super().__init__() 

1730 

1731 def axisinfo(self, unit, axis): 

1732 """ 

1733 Return the `~matplotlib.units.AxisInfo` for *unit*. 

1734 

1735 *unit* is a `~datetime.tzinfo` instance or None. 

1736 The *axis* argument is required but not used. 

1737 """ 

1738 tz = unit 

1739 

1740 majloc = AutoDateLocator(tz=tz, 

1741 interval_multiples=self._interval_multiples) 

1742 majfmt = AutoDateFormatter(majloc, tz=tz) 

1743 datemin = datetime.date(1970, 1, 1) 

1744 datemax = datetime.date(1970, 1, 2) 

1745 

1746 return units.AxisInfo(majloc=majloc, majfmt=majfmt, label='', 

1747 default_limits=(datemin, datemax)) 

1748 

1749 @staticmethod 

1750 def convert(value, unit, axis): 

1751 """ 

1752 If *value* is not already a number or sequence of numbers, convert it 

1753 with `date2num`. 

1754 

1755 The *unit* and *axis* arguments are not used. 

1756 """ 

1757 return date2num(value) 

1758 

1759 @staticmethod 

1760 def default_units(x, axis): 

1761 """ 

1762 Return the `~datetime.tzinfo` instance of *x* or of its first element, 

1763 or None 

1764 """ 

1765 if isinstance(x, np.ndarray): 

1766 x = x.ravel() 

1767 

1768 try: 

1769 x = cbook._safe_first_finite(x) 

1770 except (TypeError, StopIteration): 

1771 pass 

1772 

1773 try: 

1774 return x.tzinfo 

1775 except AttributeError: 

1776 pass 

1777 return None 

1778 

1779 

1780class ConciseDateConverter(DateConverter): 

1781 # docstring inherited 

1782 

1783 def __init__(self, formats=None, zero_formats=None, offset_formats=None, 

1784 show_offset=True, *, interval_multiples=True): 

1785 self._formats = formats 

1786 self._zero_formats = zero_formats 

1787 self._offset_formats = offset_formats 

1788 self._show_offset = show_offset 

1789 self._interval_multiples = interval_multiples 

1790 super().__init__() 

1791 

1792 def axisinfo(self, unit, axis): 

1793 # docstring inherited 

1794 tz = unit 

1795 majloc = AutoDateLocator(tz=tz, 

1796 interval_multiples=self._interval_multiples) 

1797 majfmt = ConciseDateFormatter(majloc, tz=tz, formats=self._formats, 

1798 zero_formats=self._zero_formats, 

1799 offset_formats=self._offset_formats, 

1800 show_offset=self._show_offset) 

1801 datemin = datetime.date(1970, 1, 1) 

1802 datemax = datetime.date(1970, 1, 2) 

1803 return units.AxisInfo(majloc=majloc, majfmt=majfmt, label='', 

1804 default_limits=(datemin, datemax)) 

1805 

1806 

1807class _SwitchableDateConverter: 

1808 """ 

1809 Helper converter-like object that generates and dispatches to 

1810 temporary ConciseDateConverter or DateConverter instances based on 

1811 :rc:`date.converter` and :rc:`date.interval_multiples`. 

1812 """ 

1813 

1814 @staticmethod 

1815 def _get_converter(): 

1816 converter_cls = { 

1817 "concise": ConciseDateConverter, "auto": DateConverter}[ 

1818 mpl.rcParams["date.converter"]] 

1819 interval_multiples = mpl.rcParams["date.interval_multiples"] 

1820 return converter_cls(interval_multiples=interval_multiples) 

1821 

1822 def axisinfo(self, *args, **kwargs): 

1823 return self._get_converter().axisinfo(*args, **kwargs) 

1824 

1825 def default_units(self, *args, **kwargs): 

1826 return self._get_converter().default_units(*args, **kwargs) 

1827 

1828 def convert(self, *args, **kwargs): 

1829 return self._get_converter().convert(*args, **kwargs) 

1830 

1831 

1832units.registry[np.datetime64] = \ 

1833 units.registry[datetime.date] = \ 

1834 units.registry[datetime.datetime] = \ 

1835 _SwitchableDateConverter()