88Matplotlib date format
99----------------------
1010Matplotlib represents dates using floating point numbers specifying the number
11- of days since 0001-01-01 UTC, plus 1. For example, 0001-01-01, 06:00 is 1.25,
12- not 0.25. Values < 1, i.e. dates before 0001-01-01 UTC, are not supported.
11+ of days since a defaul epoch of 0001-01-01 UTC, plus 1. For example,
12+ 0001-01-01, 06:00 is 1.25, not 0.25. (The epoch can be changed via
13+ `.dates.set_epoch` to other dates; see
14+ :doc:`/gallery/misc/date_accuracy_and_epochs` for a discussion.)
1315
1416There are a number of helper functions to convert between :mod:`datetime`
1517objects and Matplotlib dates:
2325 date2num
2426 num2date
2527 num2timedelta
26- epoch2num
27- num2epoch
2828 drange
29+ set_epoch
2930
3031.. note::
3132
155156import matplotlib .ticker as ticker
156157
157158__all__ = ('datestr2num' , 'date2num' , 'num2date' , 'num2timedelta' , 'drange' ,
158- 'epoch2num' , 'num2epoch' , 'mx2num' , 'DateFormatter' ,
159+ 'epoch2num' , 'num2epoch' , 'mx2num' , 'set_epoch' , ' DateFormatter' ,
159160 'ConciseDateFormatter' , 'IndexDateFormatter' , 'AutoDateFormatter' ,
160161 'DateLocator' , 'RRuleLocator' , 'AutoDateLocator' , 'YearLocator' ,
161162 'MonthLocator' , 'WeekdayLocator' ,
@@ -206,12 +207,42 @@ def _get_rc_timezone():
206207 MO , TU , WE , TH , FR , SA , SU )
207208WEEKDAYS = (MONDAY , TUESDAY , WEDNESDAY , THURSDAY , FRIDAY , SATURDAY , SUNDAY )
208209
210+ # default epoch: passed to np.datetime64...
209211_epoch = '0001-01-01T00:00:00'
210212
213+
211214def set_epoch (epoch ):
215+ """
216+ Set the epoch (origin for dates) for datetime calculations. The
217+ default epoch is '0000-01-01T00:00:00', but this means "modern"
218+ dates only have an accuracy of approximately 8 microseconds in the
219+ matplotlib floating point representation. Smaller floats means more
220+ precision, so an epoch within 50 days of the dates being considered
221+ can reach 1 nanosecond resolution.
222+
223+ See :doc:`/gallery/misc/date_accuracy_and_epochs` for a discussion.
224+
225+ Parameters
226+ ----------
227+ epoch : str
228+ valid UTC date parsable by `.datetime64` (do not include timezone).
229+
230+ """
212231 global _epoch
213232 _epoch = epoch
214233
234+
235+ def get_epoch ():
236+ """
237+ Get the epoch currently used by `.dates`.
238+
239+ Returns
240+ -------
241+ epoch: str
242+ """
243+ return _epoch
244+
245+
215246def _to_ordinalf (dt ):
216247 """
217248 Convert :mod:`datetime` or :mod:`date` to the Gregorian date as UTC float
@@ -244,7 +275,7 @@ def _dt64_to_ordinalf(d):
244275 # seconds. That should get out to +/-2e11 years.
245276 dseconds = d .astype ('datetime64[s]' )
246277 extra = (d - dseconds ).astype ('timedelta64[ns]' )
247- t0 = np .datetime64 (_epoch )
278+ t0 = np .datetime64 (_epoch , 's' )
248279 dt = (dseconds - t0 ).astype (np .float64 )
249280 dt += extra .astype (np .float64 ) / 1.0e9
250281 dt = dt / SEC_PER_DAY + 1.0
@@ -269,15 +300,19 @@ def _from_ordinalf(x, tz=None):
269300 timezone *tz*, or if *tz* is ``None``, in the timezone specified in
270301 :rc:`timezone`.
271302 """
303+
272304 if tz is None :
273305 tz = _get_rc_timezone ()
274306
275- dt = (np .datetime64 (_epoch ) +
307+ dt = (np .datetime64 (_epoch ) +
276308 np .timedelta64 (int ((x - 1.0 ) * MUSECONDS_PER_DAY ), 'us' ))
309+ if dt < np .datetime64 ('0001-01-01' ) or dt > np .datetime64 ('10000-01-01' ):
310+ raise ValueError (f'Matplotlib date { dt } cannot be converted to a '
311+ 'datetime.' )
312+ dt = dt .tolist ()
277313 # datetime64 is always UTC:
278- dt = dt .tolist (). replace (tzinfo = dateutil .tz .gettz ('UTC' ))
314+ dt = dt .replace (tzinfo = dateutil .tz .gettz ('UTC' ))
279315 # but maybe we are working in a different timezone so move.
280-
281316 dt = dt .astimezone (tz )
282317 if x > 30 * 365 :
283318 # if x is big, round off to nearest twenty microseconds.
@@ -590,11 +625,6 @@ def __init__(self, fmt, tz=None):
590625 self .tz = tz
591626
592627 def __call__ (self , x , pos = 0 ):
593- if x == 0 :
594- raise ValueError ('DateFormatter found a value of x=0, which is '
595- 'an illegal date; this usually occurs because '
596- 'you have not informed the axis that it is '
597- 'plotting dates, e.g., with ax.xaxis_date()' )
598628 return num2date (x , self .tz ).strftime (self .fmt )
599629
600630 def set_tzinfo (self , tz ):
@@ -1431,11 +1461,12 @@ def get_locator(self, dmin, dmax):
14311461 locator = RRuleLocator (rrule , self .tz )
14321462 else :
14331463 locator = MicrosecondLocator (interval , tz = self .tz )
1434- if dmin . year > 20 and interval < 1000 :
1464+ if date2num ( dmin ) > 30 * 365 and interval < 1000 :
14351465 cbook ._warn_external (
1436- 'Plotting microsecond time intervals is not well '
1437- 'supported; please see the MicrosecondLocator '
1438- 'documentation for details.' )
1466+ 'Plotting microsecond time intervals for dates far from '
1467+ f'the epoch (time origin: { _epoch } ) is not well-'
1468+ 'supported. See matplotlib.dates.set_epoch to change the '
1469+ 'epoch.' )
14391470
14401471 locator .set_axis (self .axis )
14411472
@@ -1673,17 +1704,17 @@ class MicrosecondLocator(DateLocator):
16731704
16741705 .. note::
16751706
1676- Due to the floating point representation of time in days since
1677- 0001-01-01 UTC (plus 1), plotting data with microsecond time
1678- resolution does not work well with current dates.
1707+ By default, Matplotlib uses a floating point representation of time in
1708+ days since 0001-01-01 UTC (plus 1), so plotting data with
1709+ microsecond time resolution does not work well with modern dates.
16791710
16801711 If you want microsecond resolution time plots, it is strongly
16811712 recommended to use floating point seconds, not datetime-like
16821713 time representation.
16831714
16841715 If you really must use datetime.datetime() or similar and still
1685- need microsecond precision, your only chance is to use very
1686- early years; using year 0001 is recommended .
1716+ need microsecond precision, change the time origin via
1717+ `.dates.set_epoch` to something closer to the dates being plotted .
16871718
16881719 """
16891720 def __init__ (self , interval = 1 , tz = None ):
@@ -1744,6 +1775,7 @@ def _get_interval(self):
17441775 return self ._interval
17451776
17461777
1778+ @cbook .deprecated ("3.2" )
17471779def epoch2num (e ):
17481780 """
17491781 Convert an epoch or sequence of epochs to the new date format,
@@ -1752,6 +1784,7 @@ def epoch2num(e):
17521784 return EPOCH_OFFSET + np .asarray (e ) / SEC_PER_DAY
17531785
17541786
1787+ @cbook .deprecated ("3.2" )
17551788def num2epoch (d ):
17561789 """
17571790 Convert days since 0001 to epoch. *d* can be a number or sequence.
0 commit comments