8
8
Matplotlib date format
9
9
----------------------
10
10
Matplotlib 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.)
13
15
14
16
There are a number of helper functions to convert between :mod:`datetime`
15
17
objects and Matplotlib dates:
23
25
date2num
24
26
num2date
25
27
num2timedelta
26
- epoch2num
27
- num2epoch
28
28
drange
29
+ set_epoch
29
30
30
31
.. note::
31
32
155
156
import matplotlib .ticker as ticker
156
157
157
158
__all__ = ('datestr2num' , 'date2num' , 'num2date' , 'num2timedelta' , 'drange' ,
158
- 'epoch2num' , 'num2epoch' , 'mx2num' , 'DateFormatter' ,
159
+ 'epoch2num' , 'num2epoch' , 'mx2num' , 'set_epoch' , ' DateFormatter' ,
159
160
'ConciseDateFormatter' , 'IndexDateFormatter' , 'AutoDateFormatter' ,
160
161
'DateLocator' , 'RRuleLocator' , 'AutoDateLocator' , 'YearLocator' ,
161
162
'MonthLocator' , 'WeekdayLocator' ,
@@ -206,12 +207,42 @@ def _get_rc_timezone():
206
207
MO , TU , WE , TH , FR , SA , SU )
207
208
WEEKDAYS = (MONDAY , TUESDAY , WEDNESDAY , THURSDAY , FRIDAY , SATURDAY , SUNDAY )
208
209
210
+ # default epoch: passed to np.datetime64...
209
211
_epoch = '0001-01-01T00:00:00'
210
212
213
+
211
214
def 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
+ """
212
231
global _epoch
213
232
_epoch = epoch
214
233
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
+
215
246
def _to_ordinalf (dt ):
216
247
"""
217
248
Convert :mod:`datetime` or :mod:`date` to the Gregorian date as UTC float
@@ -244,7 +275,7 @@ def _dt64_to_ordinalf(d):
244
275
# seconds. That should get out to +/-2e11 years.
245
276
dseconds = d .astype ('datetime64[s]' )
246
277
extra = (d - dseconds ).astype ('timedelta64[ns]' )
247
- t0 = np .datetime64 (_epoch )
278
+ t0 = np .datetime64 (_epoch , 's' )
248
279
dt = (dseconds - t0 ).astype (np .float64 )
249
280
dt += extra .astype (np .float64 ) / 1.0e9
250
281
dt = dt / SEC_PER_DAY + 1.0
@@ -269,15 +300,19 @@ def _from_ordinalf(x, tz=None):
269
300
timezone *tz*, or if *tz* is ``None``, in the timezone specified in
270
301
:rc:`timezone`.
271
302
"""
303
+
272
304
if tz is None :
273
305
tz = _get_rc_timezone ()
274
306
275
- dt = (np .datetime64 (_epoch ) +
307
+ dt = (np .datetime64 (_epoch ) +
276
308
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 ()
277
313
# datetime64 is always UTC:
278
- dt = dt .tolist (). replace (tzinfo = dateutil .tz .gettz ('UTC' ))
314
+ dt = dt .replace (tzinfo = dateutil .tz .gettz ('UTC' ))
279
315
# but maybe we are working in a different timezone so move.
280
-
281
316
dt = dt .astimezone (tz )
282
317
if x > 30 * 365 :
283
318
# if x is big, round off to nearest twenty microseconds.
@@ -590,11 +625,6 @@ def __init__(self, fmt, tz=None):
590
625
self .tz = tz
591
626
592
627
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()' )
598
628
return num2date (x , self .tz ).strftime (self .fmt )
599
629
600
630
def set_tzinfo (self , tz ):
@@ -1431,11 +1461,12 @@ def get_locator(self, dmin, dmax):
1431
1461
locator = RRuleLocator (rrule , self .tz )
1432
1462
else :
1433
1463
locator = MicrosecondLocator (interval , tz = self .tz )
1434
- if dmin . year > 20 and interval < 1000 :
1464
+ if date2num ( dmin ) > 30 * 365 and interval < 1000 :
1435
1465
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.' )
1439
1470
1440
1471
locator .set_axis (self .axis )
1441
1472
@@ -1673,17 +1704,17 @@ class MicrosecondLocator(DateLocator):
1673
1704
1674
1705
.. note::
1675
1706
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.
1679
1710
1680
1711
If you want microsecond resolution time plots, it is strongly
1681
1712
recommended to use floating point seconds, not datetime-like
1682
1713
time representation.
1683
1714
1684
1715
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 .
1687
1718
1688
1719
"""
1689
1720
def __init__ (self , interval = 1 , tz = None ):
@@ -1744,6 +1775,7 @@ def _get_interval(self):
1744
1775
return self ._interval
1745
1776
1746
1777
1778
+ @cbook .deprecated ("3.2" )
1747
1779
def epoch2num (e ):
1748
1780
"""
1749
1781
Convert an epoch or sequence of epochs to the new date format,
@@ -1752,6 +1784,7 @@ def epoch2num(e):
1752
1784
return EPOCH_OFFSET + np .asarray (e ) / SEC_PER_DAY
1753
1785
1754
1786
1787
+ @cbook .deprecated ("3.2" )
1755
1788
def num2epoch (d ):
1756
1789
"""
1757
1790
Convert days since 0001 to epoch. *d* can be a number or sequence.
0 commit comments