Skip to content

Commit 3ccaf2d

Browse files
authored
Merge pull request #9779 from jklymak/add-datenum64
ENH: support np.datenum64 in dates.py
2 parents 905465b + 109acc1 commit 3ccaf2d

File tree

2 files changed

+74
-9
lines changed

2 files changed

+74
-9
lines changed

lib/matplotlib/dates.py

+32-5
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,27 @@ def _to_ordinalf(dt):
243243
_to_ordinalf_np_vectorized = np.vectorize(_to_ordinalf)
244244

245245

246+
def _dt64_to_ordinalf(d):
247+
"""
248+
Convert `numpy.datetime64` or an ndarray of those types to Gregorian
249+
date as UTC float. Roundoff is via float64 precision. Practically:
250+
microseconds for dates between 290301 BC, 294241 AD, milliseconds for
251+
larger dates (see `numpy.datetime64`). Nanoseconds aren't possible
252+
because we do times compared to ``0001-01-01T00:00:00`` (plus one day).
253+
"""
254+
255+
# the "extra" ensures that we at least allow the dynamic range out to
256+
# seconds. That should get out to +/-2e11 years.
257+
extra = d - d.astype('datetime64[s]')
258+
extra = extra.astype('timedelta64[ns]')
259+
t0 = np.datetime64('0001-01-01T00:00:00').astype('datetime64[s]')
260+
dt = (d.astype('datetime64[s]') - t0).astype(np.float64)
261+
dt += extra.astype(np.float64) / 1.0e9
262+
dt = dt / SEC_PER_DAY + 1.0
263+
264+
return dt
265+
266+
246267
def _from_ordinalf(x, tz=None):
247268
"""
248269
Convert Gregorian float of the date, preserving hours, minutes,
@@ -354,12 +375,13 @@ def date2num(d):
354375
355376
Parameters
356377
----------
357-
d : :class:`datetime` or sequence of :class:`datetime`
378+
d : :class:`datetime` or :class:`numpy.datetime64`, or sequences of
379+
these classes.
358380
359381
Returns
360382
-------
361383
float or sequence of floats
362-
Number of days (fraction part represents hours, minutes, seconds)
384+
Number of days (fraction part represents hours, minutes, seconds, ms)
363385
since 0001-01-01 00:00:00 UTC, plus one.
364386
365387
Notes
@@ -368,6 +390,10 @@ def date2num(d):
368390
Gregorian calendar is assumed; this is not universal practice.
369391
For details see the module docstring.
370392
"""
393+
394+
if ((isinstance(d, np.ndarray) and np.issubdtype(d.dtype, np.datetime64))
395+
or isinstance(d, np.datetime64)):
396+
return _dt64_to_ordinalf(d)
371397
if not cbook.iterable(d):
372398
return _to_ordinalf(d)
373399
else:
@@ -488,8 +514,8 @@ def drange(dstart, dend, delta):
488514
*dend* are :class:`datetime` instances. *delta* is a
489515
:class:`datetime.timedelta` instance.
490516
"""
491-
f1 = _to_ordinalf(dstart)
492-
f2 = _to_ordinalf(dend)
517+
f1 = date2num(dstart)
518+
f2 = date2num(dend)
493519
step = delta.total_seconds() / SEC_PER_DAY
494520

495521
# calculate the difference between dend and dstart in times of delta
@@ -504,7 +530,7 @@ def drange(dstart, dend, delta):
504530
dinterval_end -= delta
505531
num -= 1
506532

507-
f2 = _to_ordinalf(dinterval_end) # new float-endpoint
533+
f2 = date2num(dinterval_end) # new float-endpoint
508534
return np.linspace(f1, f2, num + 1)
509535

510536
### date tickers and formatters ###
@@ -1630,5 +1656,6 @@ def default_units(x, axis):
16301656
return None
16311657

16321658

1659+
units.registry[np.datetime64] = DateConverter()
16331660
units.registry[datetime.date] = DateConverter()
16341661
units.registry[datetime.datetime] = DateConverter()

lib/matplotlib/tests/test_dates.py

+42-4
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33

44
from six.moves import map
55

6-
import datetime
7-
import warnings
8-
import tempfile
9-
import pytest
106

7+
import datetime
118
import dateutil
9+
import numpy as np
10+
import pytest
1211
import pytz
12+
import tempfile
13+
import warnings
1314

1415
try:
1516
# mock in python 3.3+
@@ -22,6 +23,43 @@
2223
import matplotlib.dates as mdates
2324

2425

26+
def test_date_numpyx():
27+
# test that numpy dates work properly...
28+
base = datetime.datetime(2017, 1, 1)
29+
time = [base + datetime.timedelta(days=x) for x in range(0, 3)]
30+
timenp = np.array(time, dtype='datetime64[ns]')
31+
data = np.array([0., 2., 1.])
32+
fig = plt.figure(figsize=(10, 2))
33+
ax = fig.add_subplot(1, 1, 1)
34+
h, = ax.plot(time, data)
35+
hnp, = ax.plot(timenp, data)
36+
assert np.array_equal(h.get_xdata(orig=False), hnp.get_xdata(orig=False))
37+
fig = plt.figure(figsize=(10, 2))
38+
ax = fig.add_subplot(1, 1, 1)
39+
h, = ax.plot(data, time)
40+
hnp, = ax.plot(data, timenp)
41+
assert np.array_equal(h.get_ydata(orig=False), hnp.get_ydata(orig=False))
42+
43+
44+
@pytest.mark.parametrize('t0', [datetime.datetime(2017, 1, 1, 0, 1, 1),
45+
46+
[datetime.datetime(2017, 1, 1, 0, 1, 1),
47+
datetime.datetime(2017, 1, 1, 1, 1, 1)],
48+
49+
[[datetime.datetime(2017, 1, 1, 0, 1, 1),
50+
datetime.datetime(2017, 1, 1, 1, 1, 1)],
51+
[datetime.datetime(2017, 1, 1, 2, 1, 1),
52+
datetime.datetime(2017, 1, 1, 3, 1, 1)]]])
53+
@pytest.mark.parametrize('dtype', ['datetime64[s]',
54+
'datetime64[us]',
55+
'datetime64[ms]'])
56+
def test_date_date2num_numpy(t0, dtype):
57+
time = mdates.date2num(t0)
58+
tnp = np.array(t0, dtype=dtype)
59+
nptime = mdates.date2num(tnp)
60+
assert np.array_equal(time, nptime)
61+
62+
2563
@image_comparison(baseline_images=['date_empty'], extensions=['png'])
2664
def test_date_empty():
2765
# make sure mpl does the right thing when told to plot dates even

0 commit comments

Comments
 (0)