8000 dates: add parameter for microsecond precision · matplotlib/matplotlib@0e79f69 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0e79f69

Browse files
committed
dates: add parameter for microsecond precision
_to_ordinalf is used by num2date to actually convert floating point numbers to datetime objects. The main purpose of this conversion is to prepare the datetimes from which tick labels are generated. Since datetimes have microsecond resolution, but the input (float) does not have microsecond precision for any modern date, a 10-microsecond "snapping distance" around full seconds was used to obtain timestamps suitable for tick labels with per-second and lower resolution. However, when higher resolution is needed (which is now possible after the introduction of Microsecond support in AutoDateLocator and -Formatter in Matplotlib 2.0), unconditional full-second distance snapping is not appropriate: It is unable to perform rounding to milliseconds, and in addition may lead to multiple identical tick labels at highest plotting time resolution. The solution implemented here is to introduce a microsecond precision parameter (`musec_prec`) to the appropriate functions and function calls, and perform rounding to the nearest multiple of the given number of microseconds. The default value of 20 offers the same effect as the previous "snapping distance" around full seconds. In addition, it can also be turned off (when set to 1) or be used to perform rounding to full milliseconds.
1 parent 3ab1193 commit 0e79f69

File tree

1 file changed

+20
-11
lines changed

1 file changed

+20
-11
lines changed

lib/matplotlib/dates.py

Lines changed: 20 additions & 11 deletions
< 485D tr class="diff-line-row">
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ def _dt64_to_ordinalf(d):
268268
return dt
269269

270270

271-
def _from_ordinalf(x, tz=None):
271+
def _from_ordinalf(x, tz=None, musec_prec=20):
272272
"""
273273
Convert Gregorian float of the date, preserving hours, minutes,
274274
seconds and microseconds. Return value is a :class:`datetime`.
@@ -277,6 +277,10 @@ def _from_ordinalf(x, tz=None):
277277
be the specified :class:`datetime` object corresponding to that time in
278278
timezone `tz`, or if `tz` is `None`, in the timezone specified in
279279
`rcParams['timezone']`.
280+
281+
Since the input date `x` float is unable to preserve microsecond
282+
precision of time representation in non-antique years, the resulting
283+
datetime is rounded to the nearest multiple of `musec_prec`.
280284
"""
281285
if tz is None:
282286
tz = _get_rc_timezone()
@@ -291,10 +295,11 @@ def _from_ordinalf(x, tz=None):
291295
microseconds=int(round(remainder * MUSECONDS_PER_DAY)))
292296

293297
# Compensate for rounding errors
294-
if dt.microsecond < 10:
295-
dt = dt.replace(microsecond=0)
296-
elif dt.microsecond > 999990:
297-
dt += datetime.timedelta(microseconds=1e6 - dt.microsecond)
298+
if musec_prec > 1:
299+
musec = datetime.timedelta(
300+
microseconds=round(
301+
dt.microsecond / float(musec_prec)) * musec_prec)
302+
dt = dt.replace(microsecond=0) + musec
298303

299304
return dt.astimezone(tz)
300305

@@ -446,7 +451,7 @@ def num2julian(n):
446451
return n + JULIAN_OFFSET
447452

448453

449-
def num2date(x, tz=None):
454+
def num2date(x, tz=None, musec_prec=20):
450455
"""
451456
Parameters
452457
----------
@@ -455,6 +460,8 @@ def num2date(x, tz=None):
455460
since 0001-01-01 00:00:00 UTC, plus one.
456461
tz : string, optional
457462
Timezone of *x* (defaults to rcparams TZ value).
463+
musec_prec : int, optional
464+
Microsecond precision of return value
458465
459466
Returns
460467
-------
@@ -473,12 +480,12 @@ def num2date(x, tz=None):
473480
if tz is None:
474481
tz = _get_rc_timezone()
475482
if not cbook.iterable(x):
476-
return _from_ordinalf(x, tz)
483+
return _from_ordinalf(x, tz, musec_prec)
477484
else:
478485
x = np.asarray(x)
479486
if not x.size:
480487
return x
481-
return _from_ordinalf_np_vectorized(x, tz).tolist()
488+
return _from_ordinalf_np_vectorized(x, tz, musec_prec).tolist()
482489

483490

484491
def _ordinalf_to_timedelta(x):
@@ -554,23 +561,25 @@ class DateFormatter(ticker.Formatter):
554561

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

557-
def __init__(self, fmt, tz=None):
564+
def __init__(self, fmt, tz=None, musec_prec=20):
558565
"""
559566
*fmt* is a :func:`strftime` format string; *tz* is the
560-
:class:`tzinfo` instance.
567+
:class:`tzinfo` instance, *musec_prec* is the microsecond
568+
rounding precision.
561569
"""
562570
if tz is None:
563571
tz = _get_rc_timezone()
564572
self.fmt = fmt
565573
self.tz = tz
574+
self.musec_prec = musec_prec
566575

567576
def __call__(self, x, pos=0):
568577
if x == 0:
569578
raise ValueError('DateFormatter found a value of x=0, which is '
570579
'an illegal date. This usually occurs because '
571580
'you have not informed the axis that it is '
572581
'plotting dates, e.g., with ax.xaxis_date()')
573-
dt = num2date(x, self.tz)
582+
dt = num2date(x, self.tz, self.musec_prec)
574583
return self.strftime(dt, self.fmt)
575584

576585
def set_tzinfo(self, tz):

0 commit comments

Comments
 (0)
0