Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" 

2Matplotlib provides sophisticated date plotting capabilities, standing on the 

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

4 

5 

6.. _date-format: 

7 

8Matplotlib date format 

9---------------------- 

10Matplotlib represents dates using floating point numbers specifying the number 

11of days since 0001-01-01 UTC, plus 1. For example, 0001-01-01, 06:00 is 1.25, 

12not 0.25. Values < 1, i.e. dates before 0001-01-01 UTC, are not supported. 

13 

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

15objects and Matplotlib dates: 

16 

17.. currentmodule:: matplotlib.dates 

18 

19.. autosummary:: 

20 :nosignatures: 

21 

22 datestr2num 

23 date2num 

24 num2date 

25 num2timedelta 

26 epoch2num 

27 num2epoch 

28 drange 

29 

30.. note:: 

31 

32 Like Python's datetime, Matplotlib uses the Gregorian calendar for all 

33 conversions between dates and floating point numbers. This practice 

34 is not universal, and calendar differences can cause confusing 

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

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

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

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

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

40 732403, whereas using the Gregorian calendar via the datetime 

41 module we find:: 

42 

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

44 Out[1]: 732401 

45 

46All the Matplotlib date converters, tickers and formatters are timezone aware. 

47If no explicit timezone is provided, :rc:`timezone` is assumed. If you want to 

48use a custom time zone, pass a `datetime.tzinfo` instance with the tz keyword 

49argument to `num2date`, `~.Axes.plot_date`, and any custom date tickers or 

50locators you create. 

51 

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

53formatters are provided in this module. See 

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

55and formatters. These are described below. 

56 

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

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

59 

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

61 

62Date tickers 

63------------ 

64 

65Most of the date tickers can locate single or multiple values. For 

66example:: 

67 

68 # import constants for the days of the week 

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

70 

71 # tick on mondays every week 

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

73 

74 # tick on mondays and saturdays 

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

76 

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

78 

79 # tick on mondays every second week 

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

81 

82The rrule locator allows completely general date ticking:: 

83 

84 # tick every 5th easter 

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

86 loc = RRuleLocator(rule) 

87 

88The available date tickers are: 

89 

90* `MicrosecondLocator`: locate microseconds 

91 

92* `SecondLocator`: locate seconds 

93 

94* `MinuteLocator`: locate minutes 

95 

96* `HourLocator`: locate hours 

97 

98* `DayLocator`: locate specified days of the month 

99 

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

101 

102* `MonthLocator`: locate months, e.g., 7 for july 

103 

104* `YearLocator`: locate years that are multiples of base 

105 

106* `RRuleLocator`: locate using a `matplotlib.dates.rrulewrapper`. 

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

108 allow almost arbitrary date tick specifications. See :doc:`rrule example 

109 </gallery/ticks_and_spines/date_demo_rrule>`. 

110 

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

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

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

114 sensible multiples of the tick intervals. E.g. if the interval is 4 hours, 

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

116 by default. 

117 

118Date formatters 

119--------------- 

120 

121The available date formatters are: 

122 

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

124 most useful when used with the `AutoDateLocator`. 

125 

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

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

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

129 

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

131 

132* `IndexDateFormatter`: date plots with implicit *x* indexing. 

133""" 

134 

135import datetime 

136import functools 

137import logging 

138import math 

139import re 

140import time 

141import warnings 

142 

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

144 MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, 

145 SECONDLY) 

146from dateutil.relativedelta import relativedelta 

147import dateutil.parser 

148import dateutil.tz 

149import numpy as np 

150 

151import matplotlib 

152from matplotlib import rcParams 

153import matplotlib.units as units 

154import matplotlib.cbook as cbook 

155import matplotlib.ticker as ticker 

156 

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

158 'epoch2num', 'num2epoch', 'mx2num', 'DateFormatter', 

159 'ConciseDateFormatter', 'IndexDateFormatter', 'AutoDateFormatter', 

160 'DateLocator', 'RRuleLocator', 'AutoDateLocator', 'YearLocator', 

161 'MonthLocator', 'WeekdayLocator', 

162 'DayLocator', 'HourLocator', 'MinuteLocator', 

163 'SecondLocator', 'MicrosecondLocator', 

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

165 'YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY', 

166 'HOURLY', 'MINUTELY', 'SECONDLY', 'MICROSECONDLY', 'relativedelta', 

167 'seconds', 'minutes', 'hours', 'weeks') 

168 

169 

170_log = logging.getLogger(__name__) 

171UTC = datetime.timezone.utc 

172 

173 

174def _get_rc_timezone(): 

175 """Retrieve the preferred timezone from the rcParams dictionary.""" 

176 s = matplotlib.rcParams['timezone'] 

177 if s == 'UTC': 

178 return UTC 

179 return dateutil.tz.gettz(s) 

180 

181 

182""" 

183Time-related constants. 

184""" 

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

186JULIAN_OFFSET = 1721424.5 # Julian date at 0001-01-01 

187MICROSECONDLY = SECONDLY + 1 

188HOURS_PER_DAY = 24. 

189MIN_PER_HOUR = 60. 

190SEC_PER_MIN = 60. 

191MONTHS_PER_YEAR = 12. 

192 

193DAYS_PER_WEEK = 7. 

194DAYS_PER_MONTH = 30. 

195DAYS_PER_YEAR = 365.0 

196 

197MINUTES_PER_DAY = MIN_PER_HOUR * HOURS_PER_DAY 

198 

199SEC_PER_HOUR = SEC_PER_MIN * MIN_PER_HOUR 

200SEC_PER_DAY = SEC_PER_HOUR * HOURS_PER_DAY 

201SEC_PER_WEEK = SEC_PER_DAY * DAYS_PER_WEEK 

202 

203MUSECONDS_PER_DAY = 1e6 * SEC_PER_DAY 

204 

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

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

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

208 

209 

210def _to_ordinalf(dt): 

211 """ 

212 Convert :mod:`datetime` or :mod:`date` to the Gregorian date as UTC float 

213 days, preserving hours, minutes, seconds and microseconds. Return value 

214 is a :func:`float`. 

215 """ 

216 # Convert to UTC 

217 tzi = getattr(dt, 'tzinfo', None) 

218 if tzi is not None: 

219 dt = dt.astimezone(UTC) 

220 tzi = UTC 

221 

222 base = float(dt.toordinal()) 

223 

224 # If it's sufficiently datetime-like, it will have a `date()` method 

225 cdate = getattr(dt, 'date', lambda: None)() 

226 if cdate is not None: 

227 # Get a datetime object at midnight UTC 

228 midnight_time = datetime.time(0, tzinfo=tzi) 

229 

230 rdt = datetime.datetime.combine(cdate, midnight_time) 

231 

232 # Append the seconds as a fraction of a day 

233 base += (dt - rdt).total_seconds() / SEC_PER_DAY 

234 

235 return base 

236 

237 

238# a version of _to_ordinalf that can operate on numpy arrays 

239_to_ordinalf_np_vectorized = np.vectorize(_to_ordinalf) 

240 

241 

242def _dt64_to_ordinalf(d): 

243 """ 

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

245 date as UTC float. Roundoff is via float64 precision. Practically: 

246 microseconds for dates between 290301 BC, 294241 AD, milliseconds for 

247 larger dates (see `numpy.datetime64`). Nanoseconds aren't possible 

248 because we do times compared to ``0001-01-01T00:00:00`` (plus one day). 

249 """ 

250 

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

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

253 extra = (d - d.astype('datetime64[s]')).astype('timedelta64[ns]') 

254 t0 = np.datetime64('0001-01-01T00:00:00', 's') 

255 dt = (d.astype('datetime64[s]') - t0).astype(np.float64) 

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

257 dt = dt / SEC_PER_DAY + 1.0 

258 

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

260 d_int = d.astype(np.int64) 

261 try: 

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

263 except TypeError: 

264 if d_int == NaT_int: 

265 dt = np.nan 

266 return dt 

267 

268 

269def _from_ordinalf(x, tz=None): 

270 """ 

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

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

273 

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

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

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

277 :rc:`timezone`. 

278 """ 

279 if tz is None: 

280 tz = _get_rc_timezone() 

281 

282 ix, remainder = divmod(x, 1) 

283 ix = int(ix) 

284 if ix < 1: 

285 raise ValueError('Cannot convert {} to a date. This often happens if ' 

286 'non-datetime values are passed to an axis that ' 

287 'expects datetime objects.'.format(ix)) 

288 dt = datetime.datetime.fromordinal(ix).replace(tzinfo=UTC) 

289 

290 # Since the input date *x* float is unable to preserve microsecond 

291 # precision of time representation in non-antique years, the 

292 # resulting datetime is rounded to the nearest multiple of 

293 # `musec_prec`. A value of 20 is appropriate for current dates. 

294 musec_prec = 20 

295 remainder_musec = int(round(remainder * MUSECONDS_PER_DAY / musec_prec) 

296 * musec_prec) 

297 

298 # For people trying to plot with full microsecond precision, enable 

299 # an early-year workaround 

300 if x < 30 * 365: 

301 remainder_musec = int(round(remainder * MUSECONDS_PER_DAY)) 

302 

303 # add hours, minutes, seconds, microseconds 

304 dt += datetime.timedelta(microseconds=remainder_musec) 

305 return dt.astimezone(tz) 

306 

307 

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

309_from_ordinalf_np_vectorized = np.vectorize(_from_ordinalf) 

310 

311 

312@cbook.deprecated( 

313 "3.1", alternative="time.strptime or dateutil.parser.parse or datestr2num") 

314class strpdate2num: 

315 """ 

316 Use this class to parse date strings to matplotlib datenums when 

317 you know the date format string of the date you are parsing. 

318 """ 

319 def __init__(self, fmt): 

320 """ 

321 Parameters 

322 ---------- 

323 fmt : any valid strptime format 

324 """ 

325 self.fmt = fmt 

326 

327 def __call__(self, s): 

328 """ 

329 Parameters 

330 ---------- 

331 s : str 

332 

333 Returns 

334 ------- 

335 date2num float 

336 """ 

337 return date2num(datetime.datetime(*time.strptime(s, self.fmt)[:6])) 

338 

339 

340@cbook.deprecated( 

341 "3.1", alternative="time.strptime or dateutil.parser.parse or datestr2num") 

342class bytespdate2num(strpdate2num): 

343 """ 

344 Use this class to parse date strings to matplotlib datenums when 

345 you know the date format string of the date you are parsing. See 

346 :doc:`/gallery/misc/load_converter.py`. 

347 """ 

348 def __init__(self, fmt, encoding='utf-8'): 

349 """ 

350 Parameters 

351 ---------- 

352 fmt : any valid strptime format 

353 encoding : str 

354 Encoding to use on byte input. 

355 """ 

356 super().__init__(fmt) 

357 self.encoding = encoding 

358 

359 def __call__(self, b): 

360 """ 

361 Parameters 

362 ---------- 

363 b : bytes 

364 

365 Returns 

366 ------- 

367 date2num float 

368 """ 

369 s = b.decode(self.encoding) 

370 return super().__call__(s) 

371 

372 

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

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

375 

376 

377def datestr2num(d, default=None): 

378 """ 

379 Convert a date string to a datenum using :func:`dateutil.parser.parse`. 

380 

381 Parameters 

382 ---------- 

383 d : str or sequence of str 

384 The dates to convert. 

385 

386 default : datetime instance, optional 

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

388 """ 

389 if isinstance(d, str): 

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

391 return date2num(dt) 

392 else: 

393 if default is not None: 

394 d = [dateutil.parser.parse(s, default=default) for s in d] 

395 d = np.asarray(d) 

396 if not d.size: 

397 return d 

398 return date2num(_dateutil_parser_parse_np_vectorized(d)) 

399 

400 

401def date2num(d): 

402 """ 

403 Convert datetime objects to Matplotlib dates. 

404 

405 Parameters 

406 ---------- 

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

408 

409 Returns 

410 ------- 

411 float or sequence of floats 

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

413 since 0001-01-01 00:00:00 UTC, plus one. 

414 

415 Notes 

416 ----- 

417 The addition of one here is a historical artifact. Also, note that the 

418 Gregorian calendar is assumed; this is not universal practice. 

419 For details see the module docstring. 

420 """ 

421 if hasattr(d, "values"): 

422 # this unpacks pandas series or dataframes... 

423 d = d.values 

424 if not np.iterable(d): 

425 if (isinstance(d, np.datetime64) or 

426 (isinstance(d, np.ndarray) and 

427 np.issubdtype(d.dtype, np.datetime64))): 

428 return _dt64_to_ordinalf(d) 

429 return _to_ordinalf(d) 

430 

431 else: 

432 d = np.asarray(d) 

433 if np.issubdtype(d.dtype, np.datetime64): 

434 return _dt64_to_ordinalf(d) 

435 if not d.size: 

436 return d 

437 return _to_ordinalf_np_vectorized(d) 

438 

439 

440def julian2num(j): 

441 """ 

442 Convert a Julian date (or sequence) to a Matplotlib date (or sequence). 

443 

444 Parameters 

445 ---------- 

446 j : float or sequence of floats 

447 Julian date(s) 

448 

449 Returns 

450 ------- 

451 float or sequence of floats 

452 Matplotlib date(s) 

453 """ 

454 return np.subtract(j, JULIAN_OFFSET) # Handles both scalar & nonscalar j. 

455 

456 

457def num2julian(n): 

458 """ 

459 Convert a Matplotlib date (or sequence) to a Julian date (or sequence). 

460 

461 Parameters 

462 ---------- 

463 n : float or sequence of floats 

464 Matplotlib date(s) 

465 

466 Returns 

467 ------- 

468 float or sequence of floats 

469 Julian date(s) 

470 """ 

471 return np.add(n, JULIAN_OFFSET) # Handles both scalar & nonscalar j. 

472 

473 

474def num2date(x, tz=None): 

475 """ 

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

477 

478 Parameters 

479 ---------- 

480 x : float or sequence of floats 

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

482 since 0001-01-01 00:00:00 UTC, plus one. 

483 tz : str, optional 

484 Timezone of *x* (defaults to rcparams ``timezone``). 

485 

486 Returns 

487 ------- 

488 `~datetime.datetime` or sequence of `~datetime.datetime` 

489 Dates are returned in timezone *tz*. 

490 

491 If *x* is a sequence, a sequence of :class:`datetime` objects will 

492 be returned. 

493 

494 Notes 

495 ----- 

496 The addition of one here is a historical artifact. Also, note that the 

497 Gregorian calendar is assumed; this is not universal practice. 

498 For details, see the module docstring. 

499 """ 

500 if tz is None: 

501 tz = _get_rc_timezone() 

502 if not np.iterable(x): 

503 return _from_ordinalf(x, tz) 

504 else: 

505 x = np.asarray(x) 

506 if not x.size: 

507 return x 

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

509 

510 

511def _ordinalf_to_timedelta(x): 

512 return datetime.timedelta(days=x) 

513 

514 

515_ordinalf_to_timedelta_np_vectorized = np.vectorize(_ordinalf_to_timedelta) 

516 

517 

518def num2timedelta(x): 

519 """ 

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

521 

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

523 be returned. 

524 

525 Parameters 

526 ---------- 

527 x : float, sequence of floats 

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

529 

530 Returns 

531 ------- 

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

533 

534 """ 

535 if not np.iterable(x): 

536 return _ordinalf_to_timedelta(x) 

537 else: 

538 x = np.asarray(x) 

539 if not x.size: 

540 return x 

541 return _ordinalf_to_timedelta_np_vectorized(x).tolist() 

542 

543 

544def drange(dstart, dend, delta): 

545 """ 

546 Return a sequence of equally spaced Matplotlib dates. 

547 

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

549 They are spaced by *delta*. 

550 

551 Parameters 

552 ---------- 

553 dstart, dend : `~datetime.datetime` 

554 The date limits. 

555 delta : `datetime.timedelta` 

556 Spacing of the dates. 

557 

558 Returns 

559 ------- 

560 drange : `numpy.array` 

561 A list floats representing Matplotlib dates. 

562 

563 """ 

564 f1 = date2num(dstart) 

565 f2 = date2num(dend) 

566 step = delta.total_seconds() / SEC_PER_DAY 

567 

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

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

570 

571 # calculate end of the interval which will be generated 

572 dinterval_end = dstart + num * delta 

573 

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

575 if dinterval_end >= dend: 

576 # if the endpoint is greater than dend, just subtract one delta 

577 dinterval_end -= delta 

578 num -= 1 

579 

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

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

582 

583## date tickers and formatters ### 

584 

585 

586class DateFormatter(ticker.Formatter): 

587 """ 

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

589 `~datetime.datetime.strftime` format string. 

590 """ 

591 

592 illegal_s = re.compile(r"((^|[^%])(%%)*%s)") 

593 

594 def __init__(self, fmt, tz=None): 

595 """ 

596 Parameters 

597 ---------- 

598 fmt : str 

599 `~datetime.datetime.strftime` format string 

600 tz : `tzinfo`, default: :rc:`timezone` 

601 Ticks timezone. 

602 """ 

603 if tz is None: 

604 tz = _get_rc_timezone() 

605 self.fmt = fmt 

606 self.tz = tz 

607 

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

609 if x == 0: 

610 raise ValueError('DateFormatter found a value of x=0, which is ' 

611 'an illegal date; this usually occurs because ' 

612 'you have not informed the axis that it is ' 

613 'plotting dates, e.g., with ax.xaxis_date()') 

614 return num2date(x, self.tz).strftime(self.fmt) 

615 

616 def set_tzinfo(self, tz): 

617 self.tz = tz 

618 

619 

620class IndexDateFormatter(ticker.Formatter): 

621 """Use with `.IndexLocator` to cycle format strings by index.""" 

622 

623 def __init__(self, t, fmt, tz=None): 

624 """ 

625 *t* is a sequence of dates (floating point days). *fmt* is a 

626 `~datetime.datetime.strftime` format string. 

627 """ 

628 if tz is None: 

629 tz = _get_rc_timezone() 

630 self.t = t 

631 self.fmt = fmt 

632 self.tz = tz 

633 

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

635 'Return the label for time *x* at position *pos*' 

636 ind = int(round(x)) 

637 if ind >= len(self.t) or ind <= 0: 

638 return '' 

639 return num2date(self.t[ind], self.tz).strftime(self.fmt) 

640 

641 

642class ConciseDateFormatter(ticker.Formatter): 

643 """ 

644 This class attempts to figure out the best format to use for the 

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

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

647 

648 >>> locator = AutoDateLocator() 

649 >>> formatter = ConciseDateFormatter(locator) 

650 

651 Parameters 

652 ---------- 

653 locator : `.ticker.Locator` 

654 Locator that this axis is using. 

655 

656 tz : str, optional 

657 Passed to `.dates.date2num`. 

658 

659 formats : list of 6 strings, optional 

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

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

662 the same format codes as `strftime`. Default is 

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

664 

665 zero_formats : list of 6 strings, optional 

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

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

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

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

670 

671 offset_formats : list of 6 strings, optional 

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

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

674 Combined with the tick labels this should completely specify the 

675 date. The default is:: 

676 

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

678 

679 show_offset : bool 

680 Whether to show the offset or not. Default is ``True``. 

681 

682 Examples 

683 -------- 

684 See :doc:`/gallery/ticks_and_spines/date_concise_formatter` 

685 

686 .. plot:: 

687 

688 import datetime 

689 import matplotlib.dates as mdates 

690 

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

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

693 for i in range(732)]) 

694 N = len(dates) 

695 np.random.seed(19680801) 

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

697 

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

699 locator = mdates.AutoDateLocator() 

700 formatter = mdates.ConciseDateFormatter(locator) 

701 ax.xaxis.set_major_locator(locator) 

702 ax.xaxis.set_major_formatter(formatter) 

703 

704 ax.plot(dates, y) 

705 ax.set_title('Concise Date Formatter') 

706 

707 """ 

708 

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

710 zero_formats=None, show_offset=True): 

711 """ 

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

713 initial string, and then redundant elements are removed. 

714 """ 

715 self._locator = locator 

716 self._tz = tz 

717 self.defaultfmt = '%Y' 

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

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

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

721 if formats: 

722 if len(formats) != 6: 

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

724 '6 format strings (or None)') 

725 self.formats = formats 

726 else: 

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

728 '%b', # ticks are mostly months 

729 '%d', # ticks are mostly days 

730 '%H:%M', # hrs 

731 '%H:%M', # min 

732 '%S.%f', # secs 

733 ] 

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

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

736 # like 1 Jan can just be labled "Jan". 02:02:00 can 

737 # just be labeled 02:02. 

738 if zero_formats: 

739 if len(zero_formats) != 6: 

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

741 '6 format strings (or None)') 

742 self.zero_formats = zero_formats 

743 elif formats: 

744 # use the users formats for the zero tick formats 

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

746 else: 

747 # make the defaults a bit nicer: 

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

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

750 

751 if offset_formats: 

752 if len(offset_formats) != 6: 

753 raise ValueError('offsetfmts argument must be a list of ' 

754 '6 format strings (or None)') 

755 self.offset_formats = offset_formats 

756 else: 

757 self.offset_formats = ['', 

758 '%Y', 

759 '%Y-%b', 

760 '%Y-%b-%d', 

761 '%Y-%b-%d', 

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

763 self.offset_string = '' 

764 self.show_offset = show_offset 

765 

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

767 formatter = DateFormatter(self.defaultfmt, self._tz) 

768 return formatter(x, pos=pos) 

769 

770 def format_ticks(self, values): 

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

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

773 

774 # basic algorithm: 

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

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

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

778 # year, month, day etc. 

779 # fmt for most ticks at this level 

780 fmts = self.formats 

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

782 zerofmts = self.zero_formats 

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

784 # or lower right of the axis. 

785 offsetfmts = self.offset_formats 

786 

787 # determine the level we will label at: 

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

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

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

791 if len(np.unique(tickdate[:, level])) > 1: 

792 break 

793 

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

795 # now loop through and decide the actual ticklabels 

796 zerovals = [0, 1, 1, 0, 0, 0, 0] 

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

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

799 if level < 5: 

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

801 fmt = zerofmts[level] 

802 else: 

803 fmt = fmts[level] 

804 else: 

805 # special handling for seconds + microseconds 

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

807 == 0): 

808 fmt = zerofmts[level] 

809 else: 

810 fmt = fmts[level] 

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

812 

813 # special handling of seconds and microseconds: 

814 # strip extra zeros and decimal if possible. 

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

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

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

818 if level >= 5: 

819 trailing_zeros = min( 

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

821 default=None) 

822 if trailing_zeros: 

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

824 if '.' in labels[nn]: 

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

826 

827 if self.show_offset: 

828 # set the offset string: 

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

830 

831 return labels 

832 

833 def get_offset(self): 

834 return self.offset_string 

835 

836 def format_data_short(self, value): 

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

838 

839 

840class AutoDateFormatter(ticker.Formatter): 

841 """ 

842 This class attempts to figure out the best format to use. This is 

843 most useful when used with the `AutoDateLocator`. 

844 

845 The AutoDateFormatter has a scale dictionary that maps the scale 

846 of the tick (the distance in days between one major tick) and a 

847 format string. The default looks like this:: 

848 

849 self.scaled = { 

850 DAYS_PER_YEAR: rcParams['date.autoformat.year'], 

851 DAYS_PER_MONTH: rcParams['date.autoformat.month'], 

852 1.0: rcParams['date.autoformat.day'], 

853 1. / HOURS_PER_DAY: rcParams['date.autoformat.hour'], 

854 1. / (MINUTES_PER_DAY): rcParams['date.autoformat.minute'], 

855 1. / (SEC_PER_DAY): rcParams['date.autoformat.second'], 

856 1. / (MUSECONDS_PER_DAY): rcParams['date.autoformat.microsecond'], 

857 } 

858 

859 

860 The algorithm picks the key in the dictionary that is >= the 

861 current scale and uses that format string. You can customize this 

862 dictionary by doing:: 

863 

864 

865 >>> locator = AutoDateLocator() 

866 >>> formatter = AutoDateFormatter(locator) 

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

868 

869 A custom `.FuncFormatter` can also be used. The following example shows 

870 how to use a custom format function to strip trailing zeros from decimal 

871 seconds and adds the date to the first ticklabel:: 

872 

873 >>> def my_format_function(x, pos=None): 

874 ... x = matplotlib.dates.num2date(x) 

875 ... if pos == 0: 

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

877 ... else: 

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

879 ... label = x.strftime(fmt) 

880 ... label = label.rstrip("0") 

881 ... label = label.rstrip(".") 

882 ... return label 

883 >>> from matplotlib.ticker import FuncFormatter 

884 >>> formatter.scaled[1/(24.*60.)] = FuncFormatter(my_format_function) 

885 """ 

886 

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

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

889 

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

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

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

893 # show all with the same priority. 

894 

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

896 # possibility... 

897 

898 def __init__(self, locator, tz=None, defaultfmt='%Y-%m-%d'): 

899 """ 

900 Autoformat the date labels. The default format is the one to use 

901 if none of the values in ``self.scaled`` are greater than the unit 

902 returned by ``locator._get_unit()``. 

903 """ 

904 self._locator = locator 

905 self._tz = tz 

906 self.defaultfmt = defaultfmt 

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

908 self.scaled = {DAYS_PER_YEAR: rcParams['date.autoformatter.year'], 

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

910 1.0: rcParams['date.autoformatter.day'], 

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

912 1. / (MINUTES_PER_DAY): 

913 rcParams['date.autoformatter.minute'], 

914 1. / (SEC_PER_DAY): 

915 rcParams['date.autoformatter.second'], 

916 1. / (MUSECONDS_PER_DAY): 

917 rcParams['date.autoformatter.microsecond']} 

918 

919 def _set_locator(self, locator): 

920 self._locator = locator 

921 

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

923 try: 

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

925 except AttributeError: 

926 locator_unit_scale = 1 

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

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

929 if scale >= locator_unit_scale), 

930 self.defaultfmt) 

931 

932 if isinstance(fmt, str): 

933 self._formatter = DateFormatter(fmt, self._tz) 

934 result = self._formatter(x, pos) 

935 elif callable(fmt): 

936 result = fmt(x, pos) 

937 else: 

938 raise TypeError('Unexpected type passed to {0!r}.'.format(self)) 

939 

940 return result 

941 

942 

943class rrulewrapper: 

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

945 kwargs['freq'] = freq 

946 self._base_tzinfo = tzinfo 

947 

948 self._update_rrule(**kwargs) 

949 

950 def set(self, **kwargs): 

951 self._construct.update(kwargs) 

952 

953 self._update_rrule(**self._construct) 

954 

955 def _update_rrule(self, **kwargs): 

956 tzinfo = self._base_tzinfo 

957 

958 # rrule does not play nicely with time zones - especially pytz time 

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

960 # datetimes are returned 

961 if 'dtstart' in kwargs: 

962 dtstart = kwargs['dtstart'] 

963 if dtstart.tzinfo is not None: 

964 if tzinfo is None: 

965 tzinfo = dtstart.tzinfo 

966 else: 

967 dtstart = dtstart.astimezone(tzinfo) 

968 

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

970 

971 if 'until' in kwargs: 

972 until = kwargs['until'] 

973 if until.tzinfo is not None: 

974 if tzinfo is not None: 

975 until = until.astimezone(tzinfo) 

976 else: 

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

978 'is naive and tzinfo is None') 

979 

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

981 

982 self._construct = kwargs.copy() 

983 self._tzinfo = tzinfo 

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

985 

986 def _attach_tzinfo(self, dt, tzinfo): 

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

988 if hasattr(tzinfo, 'localize'): 

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

990 

991 return dt.replace(tzinfo=tzinfo) 

992 

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

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

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

996 if self._tzinfo is None: 

997 return f 

998 

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

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

1001 def normalize_arg(arg): 

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

1003 if arg.tzinfo is not self._tzinfo: 

1004 arg = arg.astimezone(self._tzinfo) 

1005 

1006 return arg.replace(tzinfo=None) 

1007 

1008 return arg 

1009 

1010 def normalize_args(args, kwargs): 

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

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

1013 

1014 return args, kwargs 

1015 

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

1017 # dates and ones that return lists of dates. 

1018 if not returns_list: 

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

1020 args, kwargs = normalize_args(args, kwargs) 

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

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

1023 else: 

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

1025 args, kwargs = normalize_args(args, kwargs) 

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

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

1028 

1029 return functools.wraps(f)(inner_func) 

1030 

1031 def __getattr__(self, name): 

1032 if name in self.__dict__: 

1033 return self.__dict__[name] 

1034 

1035 f = getattr(self._rrule, name) 

1036 

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

1038 return self._aware_return_wrapper(f) 

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

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

1041 else: 

1042 return f 

1043 

1044 def __setstate__(self, state): 

1045 self.__dict__.update(state) 

1046 

1047 

1048class DateLocator(ticker.Locator): 

1049 """ 

1050 Determines the tick locations when plotting dates. 

1051 

1052 This class is subclassed by other Locators and 

1053 is not meant to be used on its own. 

1054 """ 

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

1056 

1057 def __init__(self, tz=None): 

1058 """ 

1059 *tz* is a :class:`tzinfo` instance. 

1060 """ 

1061 if tz is None: 

1062 tz = _get_rc_timezone() 

1063 self.tz = tz 

1064 

1065 def set_tzinfo(self, tz): 

1066 """ 

1067 Set time zone info. 

1068 """ 

1069 self.tz = tz 

1070 

1071 def datalim_to_dt(self): 

1072 """ 

1073 Convert axis data interval to datetime objects. 

1074 """ 

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

1076 if dmin > dmax: 

1077 dmin, dmax = dmax, dmin 

1078 if dmin < 1: 

1079 raise ValueError('datalim minimum {} is less than 1 and ' 

1080 'is an invalid Matplotlib date value. This often ' 

1081 'happens if you pass a non-datetime ' 

1082 'value to an axis that has datetime units' 

1083 .format(dmin)) 

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

1085 

1086 def viewlim_to_dt(self): 

1087 """ 

1088 Converts the view interval to datetime objects. 

1089 """ 

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

1091 if vmin > vmax: 

1092 vmin, vmax = vmax, vmin 

1093 if vmin < 1: 

1094 raise ValueError('view limit minimum {} is less than 1 and ' 

1095 'is an invalid Matplotlib date value. This ' 

1096 'often happens if you pass a non-datetime ' 

1097 'value to an axis that has datetime units' 

1098 .format(vmin)) 

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

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 2000-2010 as default. 

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

1122 date2num(datetime.date(2010, 1, 1))) 

1123 if vmax < vmin: 

1124 vmin, vmax = vmax, vmin 

1125 unit = self._get_unit() 

1126 interval = self._get_interval() 

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

1128 vmin -= 2 * unit * interval 

1129 vmax += 2 * unit * interval 

1130 return vmin, vmax 

1131 

1132 

1133class RRuleLocator(DateLocator): 

1134 # use the dateutil rrule instance 

1135 

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

1137 DateLocator.__init__(self, 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 delta = relativedelta(vmax, vmin) 

1151 

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

1153 try: 

1154 start = vmin - delta 

1155 except (ValueError, OverflowError): 

1156 start = _from_ordinalf(1.0) 

1157 

1158 try: 

1159 stop = vmax + delta 

1160 except (ValueError, OverflowError): 

1161 # The magic number! 

1162 stop = _from_ordinalf(3652059.9999999) 

1163 

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

1165 

1166 dates = self.rule.between(vmin, vmax, True) 

1167 if len(dates) == 0: 

1168 return date2num([vmin, vmax]) 

1169 return self.raise_if_exceeds(date2num(dates)) 

1170 

1171 def _get_unit(self): 

1172 """ 

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

1174 intelligent autoscaling. 

1175 """ 

1176 freq = self.rule._rrule._freq 

1177 return self.get_unit_generic(freq) 

1178 

1179 @staticmethod 

1180 def get_unit_generic(freq): 

1181 if freq == YEARLY: 

1182 return DAYS_PER_YEAR 

1183 elif freq == MONTHLY: 

1184 return DAYS_PER_MONTH 

1185 elif freq == WEEKLY: 

1186 return DAYS_PER_WEEK 

1187 elif freq == DAILY: 

1188 return 1.0 

1189 elif freq == HOURLY: 

1190 return 1.0 / HOURS_PER_DAY 

1191 elif freq == MINUTELY: 

1192 return 1.0 / MINUTES_PER_DAY 

1193 elif freq == SECONDLY: 

1194 return 1.0 / SEC_PER_DAY 

1195 else: 

1196 # error 

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

1198 

1199 def _get_interval(self): 

1200 return self.rule._rrule._interval 

1201 

1202 @cbook.deprecated("3.2") 

1203 def autoscale(self): 

1204 """ 

1205 Set the view limits to include the data range. 

1206 """ 

1207 dmin, dmax = self.datalim_to_dt() 

1208 delta = relativedelta(dmax, dmin) 

1209 

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

1211 try: 

1212 start = dmin - delta 

1213 except ValueError: 

1214 start = _from_ordinalf(1.0) 

1215 

1216 try: 

1217 stop = dmax + delta 

1218 except ValueError: 

1219 # The magic number! 

1220 stop = _from_ordinalf(3652059.9999999) 

1221 

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

1223 dmin, dmax = self.datalim_to_dt() 

1224 

1225 vmin = self.rule.before(dmin, True) 

1226 if not vmin: 

1227 vmin = dmin 

1228 

1229 vmax = self.rule.after(dmax, True) 

1230 if not vmax: 

1231 vmax = dmax 

1232 

1233 vmin = date2num(vmin) 

1234 vmax = date2num(vmax) 

1235 

1236 return self.nonsingular(vmin, vmax) 

1237 

1238 

1239class AutoDateLocator(DateLocator): 

1240 """ 

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

1242 limits and the tick locations. 

1243 

1244 Attributes 

1245 ---------- 

1246 intervald : dict 

1247 

1248 Mapping of tick frequencies (a constant from dateutil.rrule) to 

1249 multiples allowed for that ticking. The default looks like this:: 

1250 

1251 self.intervald = { 

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

1253 1000, 2000, 4000, 5000, 10000], 

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

1255 DAILY : [1, 2, 3, 7, 14], 

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

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

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

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

1260 1000, 2000, 5000, 10000, 20000, 50000, 

1261 100000, 200000, 500000, 1000000], 

1262 } 

1263 

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

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

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

1267 You can customize this dictionary by doing:: 

1268 

1269 locator = AutoDateLocator() 

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

1271 """ 

1272 

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

1274 interval_multiples=True): 

1275 """ 

1276 Parameters 

1277 ---------- 

1278 tz : `tzinfo` 

1279 Ticks timezone. 

1280 minticks : int 

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

1282 yearly, monthly, etc. 

1283 maxticks : int 

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

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

1286 control, this can be a dictionary mapping individual rrule 

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

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

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

1290 frequency not specified in this dictionary is given a default 

1291 value. 

1292 interval_multiples : bool, default: True 

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

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

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

1296 at 6 hour intervals. 

1297 """ 

1298 DateLocator.__init__(self, tz) 

1299 self._locator = YearLocator(tz=tz) 

1300 self._freq = YEARLY 

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

1302 SECONDLY, MICROSECONDLY] 

1303 self.minticks = minticks 

1304 

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

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

1307 if maxticks is not None: 

1308 try: 

1309 self.maxticks.update(maxticks) 

1310 except TypeError: 

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

1312 # number of ticks for every frequency and create a 

1313 # dictionary for this 

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

1315 self.interval_multiples = interval_multiples 

1316 self.intervald = { 

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

1318 1000, 2000, 4000, 5000, 10000], 

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

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

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

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

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

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

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

1326 1000000]} 

1327 if interval_multiples: 

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

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

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

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

1332 

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

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

1335 

1336 def __call__(self): 

1337 'Return the locations of the ticks' 

1338 self.refresh() 

1339 return self._locator() 

1340 

1341 def tick_values(self, vmin, vmax): 

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

1343 

1344 def nonsingular(self, vmin, vmax): 

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

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

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

1348 # Except if there is no data, then use 2000-2010 as default. 

1349 return (date2num(datetime.date(2000, 1, 1)), 

1350 date2num(datetime.date(2010, 1, 1))) 

1351 if vmax < vmin: 

1352 vmin, vmax = vmax, vmin 

1353 if vmin == vmax: 

1354 vmin = vmin - DAYS_PER_YEAR * 2 

1355 vmax = vmax + DAYS_PER_YEAR * 2 

1356 return vmin, vmax 

1357 

1358 def set_axis(self, axis): 

1359 DateLocator.set_axis(self, axis) 

1360 self._locator.set_axis(axis) 

1361 

1362 def refresh(self): 

1363 # docstring inherited 

1364 dmin, dmax = self.viewlim_to_dt() 

1365 self._locator = self.get_locator(dmin, dmax) 

1366 

1367 def _get_unit(self): 

1368 if self._freq in [MICROSECONDLY]: 

1369 return 1. / MUSECONDS_PER_DAY 

1370 else: 

1371 return RRuleLocator.get_unit_generic(self._freq) 

1372 

1373 @cbook.deprecated("3.2") 

1374 def autoscale(self): 

1375 'Try to choose the view limits intelligently.' 

1376 dmin, dmax = self.datalim_to_dt() 

1377 self._locator = self.get_locator(dmin, dmax) 

1378 return self._locator.autoscale() 

1379 

1380 def get_locator(self, dmin, dmax): 

1381 'Pick the best locator based on a distance.' 

1382 delta = relativedelta(dmax, dmin) 

1383 tdelta = dmax - dmin 

1384 

1385 # take absolute difference 

1386 if dmin > dmax: 

1387 delta = -delta 

1388 tdelta = -tdelta 

1389 

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

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

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

1393 # whenever possible. 

1394 numYears = float(delta.years) 

1395 numMonths = numYears * MONTHS_PER_YEAR + delta.months 

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

1397 numHours = numDays * HOURS_PER_DAY + delta.hours 

1398 numMinutes = numHours * MIN_PER_HOUR + delta.minutes 

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

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

1401 

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

1403 numSeconds, numMicroseconds] 

1404 

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

1406 

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

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

1409 # bysecond, unused (for microseconds)] 

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

1411 

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

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

1414 # an interval from an list specific to that frequency that gives no 

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

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

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

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

1419 if num < self.minticks: 

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

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

1422 # appropriate 

1423 byranges[i] = None 

1424 continue 

1425 

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

1427 # ticks 

1428 for interval in self.intervald[freq]: 

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

1430 break 

1431 else: 

1432 # We went through the whole loop without breaking, default to 

1433 # the last interval in the list and raise a warning 

1434 cbook._warn_external( 

1435 f"AutoDateLocator was unable to pick an appropriate " 

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

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

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

1439 

1440 # Set some parameters as appropriate 

1441 self._freq = freq 

1442 

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

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

1445 if i in (DAILY, WEEKLY): 

1446 if interval == 14: 

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

1448 byranges[i] = [1, 15] 

1449 elif interval == 7: 

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

1451 

1452 interval = 1 

1453 else: 

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

1455 break 

1456 else: 

1457 raise ValueError('No sensible date limit could be found in the ' 

1458 'AutoDateLocator.') 

1459 

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

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

1462 elif use_rrule_locator[i]: 

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

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

1465 dtstart=dmin, until=dmax, 

1466 bymonth=bymonth, bymonthday=bymonthday, 

1467 byhour=byhour, byminute=byminute, 

1468 bysecond=bysecond) 

1469 

1470 locator = RRuleLocator(rrule, self.tz) 

1471 else: 

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

1473 if dmin.year > 20 and interval < 1000: 

1474 cbook._warn_external( 

1475 'Plotting microsecond time intervals is not well ' 

1476 'supported; please see the MicrosecondLocator ' 

1477 'documentation for details.') 

1478 

1479 locator.set_axis(self.axis) 

1480 

1481 if self.axis is not None: 

1482 locator.set_view_interval(*self.axis.get_view_interval()) 

1483 locator.set_data_interval(*self.axis.get_data_interval()) 

1484 return locator 

1485 

1486 

1487class YearLocator(DateLocator): 

1488 """ 

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

1490 

1491 Examples:: 

1492 

1493 # Tick every year on Jan 1st 

1494 locator = YearLocator() 

1495 

1496 # Tick every 5 years on July 4th 

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

1498 """ 

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

1500 """ 

1501 Mark years that are multiple of base on a given month and day 

1502 (default jan 1). 

1503 """ 

1504 DateLocator.__init__(self, tz) 

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

1506 self.replaced = {'month': month, 

1507 'day': day, 

1508 'hour': 0, 

1509 'minute': 0, 

1510 'second': 0, 

1511 } 

1512 if not hasattr(tz, 'localize'): 

1513 # if tz is pytz, we need to do this w/ the localize fcn, 

1514 # otherwise datetime.replace works fine... 

1515 self.replaced['tzinfo'] = tz 

1516 

1517 def __call__(self): 

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

1519 try: 

1520 dmin, dmax = self.viewlim_to_dt() 

1521 except ValueError: 

1522 return [] 

1523 

1524 return self.tick_values(dmin, dmax) 

1525 

1526 def tick_values(self, vmin, vmax): 

1527 ymin = self.base.le(vmin.year) * self.base.step 

1528 ymax = self.base.ge(vmax.year) * self.base.step 

1529 

1530 vmin = vmin.replace(year=ymin, **self.replaced) 

1531 if hasattr(self.tz, 'localize'): 

1532 # look after pytz 

1533 if not vmin.tzinfo: 

1534 vmin = self.tz.localize(vmin, is_dst=True) 

1535 

1536 ticks = [vmin] 

1537 

1538 while True: 

1539 dt = ticks[-1] 

1540 if dt.year >= ymax: 

1541 return date2num(ticks) 

1542 year = dt.year + self.base.step 

1543 dt = dt.replace(year=year, **self.replaced) 

1544 if hasattr(self.tz, 'localize'): 

1545 # look after pytz 

1546 if not dt.tzinfo: 

1547 dt = self.tz.localize(dt, is_dst=True) 

1548 

1549 ticks.append(dt) 

1550 

1551 @cbook.deprecated("3.2") 

1552 def autoscale(self): 

1553 """ 

1554 Set the view limits to include the data range. 

1555 """ 

1556 dmin, dmax = self.datalim_to_dt() 

1557 

1558 ymin = self.base.le(dmin.year) 

1559 ymax = self.base.ge(dmax.year) 

1560 vmin = dmin.replace(year=ymin, **self.replaced) 

1561 vmin = vmin.astimezone(self.tz) 

1562 vmax = dmax.replace(year=ymax, **self.replaced) 

1563 vmax = vmax.astimezone(self.tz) 

1564 

1565 vmin = date2num(vmin) 

1566 vmax = date2num(vmax) 

1567 return self.nonsingular(vmin, vmax) 

1568 

1569 

1570class MonthLocator(RRuleLocator): 

1571 """ 

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

1573 """ 

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

1575 """ 

1576 Mark every month in *bymonth*; *bymonth* can be an int or 

1577 sequence. Default is ``range(1, 13)``, i.e. every month. 

1578 

1579 *interval* is the interval between each iteration. For 

1580 example, if ``interval=2``, mark every second occurrence. 

1581 """ 

1582 if bymonth is None: 

1583 bymonth = range(1, 13) 

1584 elif isinstance(bymonth, np.ndarray): 

1585 # This fixes a bug in dateutil <= 2.3 which prevents the use of 

1586 # numpy arrays in (among other things) the bymonthday, byweekday 

1587 # and bymonth parameters. 

1588 bymonth = [x.item() for x in bymonth.astype(int)] 

1589 

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

1591 interval=interval, **self.hms0d) 

1592 RRuleLocator.__init__(self, rule, tz) 

1593 

1594 

1595class WeekdayLocator(RRuleLocator): 

1596 """ 

1597 Make ticks on occurrences of each weekday. 

1598 """ 

1599 

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

1601 """ 

1602 Mark every weekday in *byweekday*; *byweekday* can be a number or 

1603 sequence. 

1604 

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

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

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

1608 

1609 *interval* specifies the number of weeks to skip. For example, 

1610 ``interval=2`` plots every second week. 

1611 """ 

1612 if isinstance(byweekday, np.ndarray): 

1613 # This fixes a bug in dateutil <= 2.3 which prevents the use of 

1614 # numpy arrays in (among other things) the bymonthday, byweekday 

1615 # and bymonth parameters. 

1616 [x.item() for x in byweekday.astype(int)] 

1617 

1618 rule = rrulewrapper(DAILY, byweekday=byweekday, 

1619 interval=interval, **self.hms0d) 

1620 RRuleLocator.__init__(self, rule, tz) 

1621 

1622 

1623class DayLocator(RRuleLocator): 

1624 """ 

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

1626 1, 15, 30. 

1627 """ 

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

1629 """ 

1630 Mark every day in *bymonthday*; *bymonthday* can be an int or sequence. 

1631 

1632 Default is to tick every day of the month: ``bymonthday=range(1, 32)``. 

1633 """ 

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

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

1636 if bymonthday is None: 

1637 bymonthday = range(1, 32) 

1638 elif isinstance(bymonthday, np.ndarray): 

1639 # This fixes a bug in dateutil <= 2.3 which prevents the use of 

1640 # numpy arrays in (among other things) the bymonthday, byweekday 

1641 # and bymonth parameters. 

1642 bymonthday = [x.item() for x in bymonthday.astype(int)] 

1643 

1644 rule = rrulewrapper(DAILY, bymonthday=bymonthday, 

1645 interval=interval, **self.hms0d) 

1646 RRuleLocator.__init__(self, rule, tz) 

1647 

1648 

1649class HourLocator(RRuleLocator): 

1650 """ 

1651 Make ticks on occurrences of each hour. 

1652 """ 

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

1654 """ 

1655 Mark every hour in *byhour*; *byhour* can be an int or sequence. 

1656 Default is to tick every hour: ``byhour=range(24)`` 

1657 

1658 *interval* is the interval between each iteration. For 

1659 example, if ``interval=2``, mark every second occurrence. 

1660 """ 

1661 if byhour is None: 

1662 byhour = range(24) 

1663 

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

1665 byminute=0, bysecond=0) 

1666 RRuleLocator.__init__(self, rule, tz) 

1667 

1668 

1669class MinuteLocator(RRuleLocator): 

1670 """ 

1671 Make ticks on occurrences of each minute. 

1672 """ 

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

1674 """ 

1675 Mark every minute in *byminute*; *byminute* can be an int or 

1676 sequence. Default is to tick every minute: ``byminute=range(60)`` 

1677 

1678 *interval* is the interval between each iteration. For 

1679 example, if ``interval=2``, mark every second occurrence. 

1680 """ 

1681 if byminute is None: 

1682 byminute = range(60) 

1683 

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

1685 bysecond=0) 

1686 RRuleLocator.__init__(self, rule, tz) 

1687 

1688 

1689class SecondLocator(RRuleLocator): 

1690 """ 

1691 Make ticks on occurrences of each second. 

1692 """ 

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

1694 """ 

1695 Mark every second in *bysecond*; *bysecond* can be an int or 

1696 sequence. Default is to tick every second: ``bysecond = range(60)`` 

1697 

1698 *interval* is the interval between each iteration. For 

1699 example, if ``interval=2``, mark every second occurrence. 

1700 

1701 """ 

1702 if bysecond is None: 

1703 bysecond = range(60) 

1704 

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

1706 RRuleLocator.__init__(self, rule, tz) 

1707 

1708 

1709class MicrosecondLocator(DateLocator): 

1710 """ 

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

1712 

1713 .. note:: 

1714 

1715 Due to the floating point representation of time in days since 

1716 0001-01-01 UTC (plus 1), plotting data with microsecond time 

1717 resolution does not work well with current dates. 

1718 

1719 If you want microsecond resolution time plots, it is strongly 

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

1721 time representation. 

1722 

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

1724 need microsecond precision, your only chance is to use very 

1725 early years; using year 0001 is recommended. 

1726 

1727 """ 

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

1729 """ 

1730 *interval* is the interval between each iteration. For 

1731 example, if ``interval=2``, mark every second microsecond. 

1732 

1733 """ 

1734 self._interval = interval 

1735 self._wrapped_locator = ticker.MultipleLocator(interval) 

1736 self.tz = tz 

1737 

1738 def set_axis(self, axis): 

1739 self._wrapped_locator.set_axis(axis) 

1740 return DateLocator.set_axis(self, axis) 

1741 

1742 def set_view_interval(self, vmin, vmax): 

1743 self._wrapped_locator.set_view_interval(vmin, vmax) 

1744 return DateLocator.set_view_interval(self, vmin, vmax) 

1745 

1746 def set_data_interval(self, vmin, vmax): 

1747 self._wrapped_locator.set_data_interval(vmin, vmax) 

1748 return DateLocator.set_data_interval(self, vmin, vmax) 

1749 

1750 def __call__(self): 

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

1752 try: 

1753 dmin, dmax = self.viewlim_to_dt() 

1754 except ValueError: 

1755 return [] 

1756 

1757 return self.tick_values(dmin, dmax) 

1758 

1759 def tick_values(self, vmin, vmax): 

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

1761 nmin *= MUSECONDS_PER_DAY 

1762 nmax *= MUSECONDS_PER_DAY 

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

1764 ticks = [tick / MUSECONDS_PER_DAY for tick in ticks] 

1765 return ticks 

1766 

1767 def _get_unit(self): 

1768 """ 

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

1770 intelligent autoscaling. 

1771 """ 

1772 return 1. / MUSECONDS_PER_DAY 

1773 

1774 def _get_interval(self): 

1775 """ 

1776 Return the number of units for each tick. 

1777 """ 

1778 return self._interval 

1779 

1780 

1781def epoch2num(e): 

1782 """ 

1783 Convert an epoch or sequence of epochs to the new date format, 

1784 that is days since 0001. 

1785 """ 

1786 return EPOCH_OFFSET + np.asarray(e) / SEC_PER_DAY 

1787 

1788 

1789def num2epoch(d): 

1790 """ 

1791 Convert days since 0001 to epoch. *d* can be a number or sequence. 

1792 """ 

1793 return (np.asarray(d) - EPOCH_OFFSET) * SEC_PER_DAY 

1794 

1795 

1796@cbook.deprecated("3.2") 

1797def mx2num(mxdates): 

1798 """ 

1799 Convert mx :class:`datetime` instance (or sequence of mx 

1800 instances) to the new date format. 

1801 """ 

1802 scalar = False 

1803 if not np.iterable(mxdates): 

1804 scalar = True 

1805 mxdates = [mxdates] 

1806 ret = epoch2num([m.ticks() for m in mxdates]) 

1807 if scalar: 

1808 return ret[0] 

1809 else: 

1810 return ret 

1811 

1812 

1813def date_ticker_factory(span, tz=None, numticks=5): 

1814 """ 

1815 Create a date locator with *numticks* (approx) and a date formatter 

1816 for *span* in days. Return value is (locator, formatter). 

1817 """ 

1818 

1819 if span == 0: 

1820 span = 1 / HOURS_PER_DAY 

1821 

1822 mins = span * MINUTES_PER_DAY 

1823 hrs = span * HOURS_PER_DAY 

1824 days = span 

1825 wks = span / DAYS_PER_WEEK 

1826 months = span / DAYS_PER_MONTH # Approx 

1827 years = span / DAYS_PER_YEAR # Approx 

1828 

1829 if years > numticks: 

1830 locator = YearLocator(int(years / numticks), tz=tz) # define 

1831 fmt = '%Y' 

1832 elif months > numticks: 

1833 locator = MonthLocator(tz=tz) 

1834 fmt = '%b %Y' 

1835 elif wks > numticks: 

1836 locator = WeekdayLocator(tz=tz) 

1837 fmt = '%a, %b %d' 

1838 elif days > numticks: 

1839 locator = DayLocator(interval=math.ceil(days / numticks), tz=tz) 

1840 fmt = '%b %d' 

1841 elif hrs > numticks: 

1842 locator = HourLocator(interval=math.ceil(hrs / numticks), tz=tz) 

1843 fmt = '%H:%M\n%b %d' 

1844 elif mins > numticks: 

1845 locator = MinuteLocator(interval=math.ceil(mins / numticks), tz=tz) 

1846 fmt = '%H:%M:%S' 

1847 else: 

1848 locator = MinuteLocator(tz=tz) 

1849 fmt = '%H:%M:%S' 

1850 

1851 formatter = DateFormatter(fmt, tz=tz) 

1852 return locator, formatter 

1853 

1854 

1855@cbook.deprecated("3.1") 

1856def seconds(s): 

1857 """ 

1858 Return seconds as days. 

1859 """ 

1860 return s / SEC_PER_DAY 

1861 

1862 

1863@cbook.deprecated("3.1") 

1864def minutes(m): 

1865 """ 

1866 Return minutes as days. 

1867 """ 

1868 return m / MINUTES_PER_DAY 

1869 

1870 

1871@cbook.deprecated("3.1") 

1872def hours(h): 

1873 """ 

1874 Return hours as days. 

1875 """ 

1876 return h / HOURS_PER_DAY 

1877 

1878 

1879@cbook.deprecated("3.1") 

1880def weeks(w): 

1881 """ 

1882 Return weeks as days. 

1883 """ 

1884 return w * DAYS_PER_WEEK 

1885 

1886 

1887class DateConverter(units.ConversionInterface): 

1888 """ 

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

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

1891 

1892 The 'unit' tag for such data is None or a tzinfo instance. 

1893 """ 

1894 

1895 @staticmethod 

1896 def axisinfo(unit, axis): 

1897 """ 

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

1899 

1900 *unit* is a tzinfo instance or None. 

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

1902 """ 

1903 tz = unit 

1904 

1905 majloc = AutoDateLocator(tz=tz) 

1906 majfmt = AutoDateFormatter(majloc, tz=tz) 

1907 datemin = datetime.date(2000, 1, 1) 

1908 datemax = datetime.date(2010, 1, 1) 

1909 

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

1911 default_limits=(datemin, datemax)) 

1912 

1913 @staticmethod 

1914 def convert(value, unit, axis): 

1915 """ 

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

1917 with `date2num`. 

1918 

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

1920 """ 

1921 return date2num(value) 

1922 

1923 @staticmethod 

1924 def default_units(x, axis): 

1925 """ 

1926 Return the tzinfo instance of *x* or of its first element, or None 

1927 """ 

1928 if isinstance(x, np.ndarray): 

1929 x = x.ravel() 

1930 

1931 try: 

1932 x = cbook.safe_first_element(x) 

1933 except (TypeError, StopIteration): 

1934 pass 

1935 

1936 try: 

1937 return x.tzinfo 

1938 except AttributeError: 

1939 pass 

1940 return None 

1941 

1942 

1943class ConciseDateConverter(DateConverter): 

1944 # docstring inherited 

1945 

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

1947 show_offset=True): 

1948 self._formats = formats 

1949 self._zero_formats = zero_formats 

1950 self._offset_formats = offset_formats 

1951 self._show_offset = show_offset 

1952 super().__init__() 

1953 

1954 def axisinfo(self, unit, axis): 

1955 # docstring inherited 

1956 tz = unit 

1957 majloc = AutoDateLocator(tz=tz) 

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

1959 zero_formats=self._zero_formats, 

1960 offset_formats=self._offset_formats, 

1961 show_offset=self._show_offset) 

1962 datemin = datetime.date(2000, 1, 1) 

1963 datemax = datetime.date(2010, 1, 1) 

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

1965 default_limits=(datemin, datemax)) 

1966 

1967 

1968units.registry[np.datetime64] = DateConverter() 

1969units.registry[datetime.date] = DateConverter() 

1970units.registry[datetime.datetime] = DateConverter()