Skip to content

Commit 4f92db3

Browse files
authored
BUG: DatetimeIndex.resolution with nanosecond reso (#46903)
1 parent 281d650 commit 4f92db3

File tree

7 files changed

+56
-15
lines changed

7 files changed

+56
-15
lines changed

doc/source/whatsnew/v1.5.0.rst

+3
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,8 @@ Other Deprecations
600600
- Deprecated the ``closed`` argument in :meth:`interval_range` in favor of ``inclusive`` argument; In a future version passing ``closed`` will raise (:issue:`40245`)
601601
- Deprecated the methods :meth:`DataFrame.mad`, :meth:`Series.mad`, and the corresponding groupby methods (:issue:`11787`)
602602
- Deprecated positional arguments to :meth:`Index.join` except for ``other``, use keyword-only arguments instead of positional arguments (:issue:`46518`)
603+
- Deprecated indexing on a timezone-naive :class:`DatetimeIndex` using a string representing a timezone-aware datetime (:issue:`46903`, :issue:`36148`)
604+
-
603605

604606
.. ---------------------------------------------------------------------------
605607
.. _whatsnew_150.performance:
@@ -641,6 +643,7 @@ Datetimelike
641643
- Bug in :meth:`Index.astype` when casting from object dtype to ``timedelta64[ns]`` dtype incorrectly casting ``np.datetime64("NaT")`` values to ``np.timedelta64("NaT")`` instead of raising (:issue:`45722`)
642644
- Bug in :meth:`SeriesGroupBy.value_counts` index when passing categorical column (:issue:`44324`)
643645
- Bug in :meth:`DatetimeIndex.tz_localize` localizing to UTC failing to make a copy of the underlying data (:issue:`46460`)
646+
- Bug in :meth:`DatetimeIndex.resolution` incorrectly returning "day" instead of "nanosecond" for nanosecond-resolution indexes (:issue:`46903`)
644647
-
645648

646649
Timedelta

pandas/_libs/tslib.pyx

+11-12
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ from pandas._libs.tslibs.nattype cimport (
6060
)
6161
from pandas._libs.tslibs.timestamps cimport _Timestamp
6262

63+
from pandas._libs.tslibs import (
64+
Resolution,
65+
get_resolution,
66+
)
6367
from pandas._libs.tslibs.timestamps import Timestamp
6468

6569
# Note: this is the only non-tslibs intra-pandas dependency here
@@ -122,11 +126,11 @@ def format_array_from_datetime(
122126
"""
123127
cdef:
124128
int64_t val, ns, N = len(values)
125-
ndarray[int64_t] consider_values
126129
bint show_ms = False, show_us = False, show_ns = False
127130
bint basic_format = False
128131
ndarray[object] result = cnp.PyArray_EMPTY(values.ndim, values.shape, cnp.NPY_OBJECT, 0)
129-
object ts, res
132+
_Timestamp ts
133+
str res
130134
npy_datetimestruct dts
131135

132136
if na_rep is None:
@@ -136,16 +140,10 @@ def format_array_from_datetime(
136140
# a format based on precision
137141
basic_format = format is None and tz is None
138142
if basic_format:
139-
consider_values = values[values != NPY_NAT]
140-
show_ns = (consider_values % 1000).any()
141-
142-
if not show_ns:
143-
consider_values //= 1000
144-
show_us = (consider_values % 1000).any()
145-
146-
if not show_ms:
147-
consider_values //= 1000
148-
show_ms = (consider_values % 1000).any()
143+
reso_obj = get_resolution(values)
144+
show_ns = reso_obj == Resolution.RESO_NS
145+
show_us = reso_obj == Resolution.RESO_US
146+
show_ms = reso_obj == Resolution.RESO_MS
149147

150148
for i in range(N):
151149
val = values[i]
@@ -178,6 +176,7 @@ def format_array_from_datetime(
178176
# invalid format string
179177
# requires dates > 1900
180178
try:
179+
# Note: dispatches to pydatetime
181180
result[i] = ts.strftime(format)
182181
except ValueError:
183182
result[i] = str(ts)

pandas/_libs/tslibs/vectorized.pyx

+3-1
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,9 @@ def ints_to_pydatetime(
204204

205205

206206
cdef inline c_Resolution _reso_stamp(npy_datetimestruct *dts):
207-
if dts.us != 0:
207+
if dts.ps != 0:
208+
return c_Resolution.RESO_NS
209+
elif dts.us != 0:
208210
if dts.us % 1000 == 0:
209211
return c_Resolution.RESO_MS
210212
return c_Resolution.RESO_US

pandas/core/indexes/datetimes.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -593,7 +593,7 @@ def _parsed_string_to_bounds(self, reso: Resolution, parsed: datetime):
593593
end = self._maybe_cast_for_get_loc(end)
594594
return start, end
595595

596-
def _deprecate_mismatched_indexing(self, key) -> None:
596+
def _deprecate_mismatched_indexing(self, key, one_way: bool = False) -> None:
597597
# GH#36148
598598
# we get here with isinstance(key, self._data._recognized_scalars)
599599
try:
@@ -606,6 +606,10 @@ def _deprecate_mismatched_indexing(self, key) -> None:
606606
"raise KeyError in a future version. "
607607
"Use a timezone-naive object instead."
608608
)
609+
elif one_way:
610+
# we special-case timezone-naive strings and timezone-aware
611+
# DatetimeIndex
612+
return
609613
else:
610614
msg = (
611615
"Indexing a timezone-aware DatetimeIndex with a "
@@ -640,6 +644,7 @@ def get_loc(self, key, method=None, tolerance=None):
640644
parsed, reso = self._parse_with_reso(key)
641645
except ValueError as err:
642646
raise KeyError(key) from err
647+
self._deprecate_mismatched_indexing(parsed, one_way=True)
643648

644649
if self._can_partial_date_slice(reso):
645650
try:

pandas/tests/indexing/test_datetime.py

+16
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,22 @@
1010

1111

1212
class TestDatetimeIndex:
13+
def test_get_loc_naive_dti_aware_str_deprecated(self):
14+
# GH#46903
15+
ts = Timestamp("20130101").value
16+
dti = pd.DatetimeIndex([ts + 50 + i for i in range(100)])
17+
ser = Series(range(100), index=dti)
18+
19+
key = "2013-01-01 00:00:00.000000050+0000"
20+
msg = "Indexing a timezone-naive DatetimeIndex with a timezone-aware datetime"
21+
with tm.assert_produces_warning(FutureWarning, match=msg):
22+
res = ser[key]
23+
assert res == 0
24+
25+
with tm.assert_produces_warning(FutureWarning, match=msg):
26+
loc = dti.get_loc(key)
27+
assert loc == 0
28+
1329
def test_indexing_with_datetime_tz(self):
1430

1531
# GH#8260

pandas/tests/series/methods/test_asof.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,12 @@ def test_asof_nanosecond_index_access(self):
2323

2424
first_value = ser.asof(ser.index[0])
2525

26+
# GH#46903 previously incorrectly was "day"
27+
assert dti.resolution == "nanosecond"
28+
2629
# this used to not work bc parsing was done by dateutil that didn't
2730
# handle nanoseconds
28-
assert first_value == ser["2013-01-01 00:00:00.000000050+0000"]
31+
assert first_value == ser["2013-01-01 00:00:00.000000050"]
2932

3033
expected_ts = np.datetime64("2013-01-01 00:00:00.000000050", "ns")
3134
assert first_value == ser[Timestamp(expected_ts)]
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import numpy as np
2+
3+
from pandas._libs.tslibs import (
4+
Resolution,
5+
get_resolution,
6+
)
7+
8+
9+
def test_get_resolution_nano():
10+
# don't return the fallback RESO_DAY
11+
arr = np.array([1], dtype=np.int64)
12+
res = get_resolution(arr)
13+
assert res == Resolution.RESO_NS

0 commit comments

Comments
 (0)