8000 improve sub-second datetime plotting and documentation (#10076) · matplotlib/matplotlib@ac2da0e · GitHub
[go: up one dir, main page]

Skip to content

Commit ac2da0e

Browse files
nmartensenjklymak
authored andcommitted
improve sub-second datetime plotting and documentation (#10076)
1 parent abaedab commit ac2da0e

File tree

3 files changed

+49
-19
lines changed

3 files changed

+49
-19
lines changed

lib/matplotlib/dates.py

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,12 @@
115115
<../gallery/ticks_and_spines/date_demo_rrule.html>`_.
116116
117117
* :class:`AutoDateLocator`: On autoscale, this class picks the best
118-
:class:`RRuleLocator` to set the view limits and the tick
118+
:class:`DateLocator` (e.g., :class:`RRuleLocator`)
119+
to set the view limits and the tick
119120
locations. If called with ``interval_multiples=True`` it will
120121
make ticks line up with sensible multiples of the tick intervals. E.g.
121122
if the interval is 4 hours, it will pick hours 0, 4, 8, etc as ticks.
122-
This behaviour is not garaunteed by default.
123+
This behaviour is not guaranteed by default.
123124
124125
Date formatters
125126
---------------
@@ -146,6 +147,7 @@
146147
import functools
147148

148149
import warnings
150+
import logging
149151

150152
from dateutil.rrule import (rrule, MO, TU, WE, TH, FR, SA, SU, YEARLY,
151153
MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY,
@@ -177,6 +179,9 @@
177179
'seconds', 'minutes', 'hours', 'weeks')
178180

179181

182+
_log = logging.getLogger(__name__)
183+
184+
180185
# Make a simple UTC instance so we don't always have to import
181186
# pytz. From the python datetime library docs:
182187

@@ -311,14 +316,21 @@ def _from_ordinalf(x, tz=None):
311316

312317
remainder = float(x) - ix
313318

314-
# Round down to the nearest microsecond.
315-
dt += datetime.timedelta(microseconds=int(remainder * MUSECONDS_PER_DAY))
319+
# Since the input date `x` float is unable to preserve microsecond
320+
# precision of time representation in non-antique years, the
321+
# resulting datetime is rounded to the nearest multiple of
322+
# `musec_prec`. A value of 20 is appropriate for current dates.
323+
musec_prec = 20
324+
remainder_musec = int(round(remainder * MUSECONDS_PER_DAY /
325+
float(musec_prec)) * musec_prec)
326+
327+
# For people trying to plot with full microsecond precision, enable
328+
# an early-year workaround
329+
if x < 30 * 365:
330+
remainder_musec = int(round(remainder * MUSECONDS_PER_DAY))
316331

317-
# Compensate for rounding errors
318-
if dt.microsecond < 10:
319-
dt = dt.replace(microsecond=0)
320-
elif dt.microsecond > 999990:
321-
dt += datetime.timedelta(microseconds=1e6 - dt.microsecond)
332+
# add hours, minutes, seconds, microseconds
333+
dt += datetime.timedelta(microseconds=remainder_musec)
322334

323335
return dt.astimezone(tz)
324336

@@ -1347,6 +1359,11 @@ def get_locator(self, dmin, dmax):
13471359
locator = RRuleLocator(rrule, self.tz)
13481360
else:
13491361
locator = MicrosecondLocator(interval, tz=self.tz)
1362+
if dmin.year > 20 and interval < 1000:
1363+
_log.warn('Plotting microsecond time intervals is not'
1364+
' well supported. Please see the'
1365+
' MicrosecondLocator documentation'
1366+
' for details.')
13501367

13511368
locator.set_axis(self.axis)
13521369

@@ -1562,7 +1579,21 @@ def __init__(self, bysecond=None, interval=1, tz=None):
15621579

15631580
class MicrosecondLocator(DateLocator):
15641581
"""
1565-
Make ticks on occurances of each microsecond.
1582+
Make ticks on regular intervals of one or more microsecond(s).
1583+
1584+
.. note::
1585+
1586+
Due to the floating point representation of time in days since
1587+
0001-01-01 UTC (plus 1), plotting data with microsecond time
1588+
resolution does not work well with current dates.
1589+
1590+
If you want microsecond resolution time plots, it is strongly
1591+
recommended to use floating point seconds, not datetime-like
1592+
time representation.
1593+
1594+
If you really must use datetime.datetime() or similar and still
1595+
need microsecond precision, your only chance is to use very
1596+
early years; using year 0001 is recommended.
15661597
15671598
"""
15681599
def __init__(self, interval=1, tz=None):
Loading

lib/matplotlib/tests/test_dates.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,6 @@ def _create_auto_date_locator(date1, date2):
355355
['1990-01-01 00:00:00+00:00', '1990-01-01 00:05:00+00:00',
356356
'1990-01-01 00:10:00+00:00', '1990-01-01 00:15:00+00:00',
357357
'1990-01-01 00:20:00+00:00']
358-
359358
],
360359
[datetime.timedelta(seconds=40),
361360
['1990-01-01 00:00:00+00:00', '1990-01-01 00:00:05+00:00',
@@ -365,11 +364,11 @@ def _create_auto_date_locator(date1, date2):
365364
'1990-01-01 00:00:40+00:00']
366365
],
367366
[datetime.timedelta(microseconds=1500),
368-
['1989-12-31 23:59:59.999507+00:00',
367+
['1989-12-31 23:59:59.999500+00:00',
369368
'1990-01-01 00:00:00+00:00',
370-
'1990-01-01 00:00:00.000502+00:00',
371-
'1990-01-01 00:00:00.001005+00:00',
372-
'1990-01-01 00:00:00.001508+00:00']
369+
'1990-01-01 00:00:00.000500+00:00',
370+
'1990-01-01 00:00:00.001000+00:00',
371+
'1990-01-01 00:00:00.001500+00:00']
373372
],
374373
)
375374

@@ -438,11 +437,11 @@ def _create_auto_date_locator(date1, date2):
438437
'1997-01-01 00:00:40+00:00']
439438
],
440439
[datetime.timedelta(microseconds=1500),
441-
['1996-12-31 23:59:59.999507+00:00',
440+
['1996-12-31 23:59:59.999500+00:00',
442441
'1997-01-01 00:00:00+00:00',
443-
'1997-01-01 00:00:00.000502+00:00',
444-
'1997-01-01 00:00:00.001005+00:00',
445-
'1997-01-01 00:00:00.001508+00:00']
442+
'1997-01-01 00:00:00.000500+00:00',
443+
'1997-01-01 00:00:00.001000+00:00',
444+
'1997-01-01 00:00:00.001500+00:00']
446445
],
447446
)
448447

0 commit comments

Comments
 (0)
0