Skip to content

Commit

Permalink
BUG: partial-timestamp slicing near the end of year/quarter/month (#3…
Browse files Browse the repository at this point in the history
  • Loading branch information
jbrockmendel authored and jreback committed Jan 18, 2020
1 parent 1c9f23c commit 65b23c2
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 11 deletions.
2 changes: 1 addition & 1 deletion doc/source/whatsnew/v1.1.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ Interval

Indexing
^^^^^^^^

- Bug in slicing on a :class:`DatetimeIndex` with a partial-timestamp dropping high-resolution indices near the end of a year, quarter, or month (:issue:`31064`)
-
-

Expand Down
27 changes: 17 additions & 10 deletions pandas/core/indexes/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@

import numpy as np

from pandas._libs import NaT, Timestamp, index as libindex, lib, tslib as libts
from pandas._libs import (
NaT,
Timedelta,
Timestamp,
index as libindex,
lib,
tslib as libts,
)
from pandas._libs.tslibs import ccalendar, fields, parsing, timezones
from pandas.util._decorators import Appender, Substitution, cache_readonly

Expand All @@ -31,7 +38,7 @@
import pandas.core.tools.datetimes as tools

from pandas.tseries.frequencies import Resolution, to_offset
from pandas.tseries.offsets import Nano, prefix_mapping
from pandas.tseries.offsets import prefix_mapping


def _new_DatetimeIndex(cls, d):
Expand Down Expand Up @@ -519,27 +526,27 @@ def _parsed_string_to_bounds(self, reso, parsed):
raise KeyError
if reso == "year":
start = Timestamp(parsed.year, 1, 1)
end = Timestamp(parsed.year, 12, 31, 23, 59, 59, 999999)
end = Timestamp(parsed.year + 1, 1, 1) - Timedelta(nanoseconds=1)
elif reso == "month":
d = ccalendar.get_days_in_month(parsed.year, parsed.month)
start = Timestamp(parsed.year, parsed.month, 1)
end = Timestamp(parsed.year, parsed.month, d, 23, 59, 59, 999999)
end = start + Timedelta(days=d, nanoseconds=-1)
elif reso == "quarter":
qe = (((parsed.month - 1) + 2) % 12) + 1 # two months ahead
d = ccalendar.get_days_in_month(parsed.year, qe) # at end of month
start = Timestamp(parsed.year, parsed.month, 1)
end = Timestamp(parsed.year, qe, d, 23, 59, 59, 999999)
end = Timestamp(parsed.year, qe, 1) + Timedelta(days=d, nanoseconds=-1)
elif reso == "day":
start = Timestamp(parsed.year, parsed.month, parsed.day)
end = start + timedelta(days=1) - Nano(1)
end = start + Timedelta(days=1, nanoseconds=-1)
elif reso == "hour":
start = Timestamp(parsed.year, parsed.month, parsed.day, parsed.hour)
end = start + timedelta(hours=1) - Nano(1)
end = start + Timedelta(hours=1, nanoseconds=-1)
elif reso == "minute":
start = Timestamp(
parsed.year, parsed.month, parsed.day, parsed.hour, parsed.minute
)
end = start + timedelta(minutes=1) - Nano(1)
end = start + Timedelta(minutes=1, nanoseconds=-1)
elif reso == "second":
start = Timestamp(
parsed.year,
Expand All @@ -549,7 +556,7 @@ def _parsed_string_to_bounds(self, reso, parsed):
parsed.minute,
parsed.second,
)
end = start + timedelta(seconds=1) - Nano(1)
end = start + Timedelta(seconds=1, nanoseconds=-1)
elif reso == "microsecond":
start = Timestamp(
parsed.year,
Expand All @@ -560,7 +567,7 @@ def _parsed_string_to_bounds(self, reso, parsed):
parsed.second,
parsed.microsecond,
)
end = start + timedelta(microseconds=1) - Nano(1)
end = start + Timedelta(microseconds=1, nanoseconds=-1)
# GH 24076
# If an incoming date string contained a UTC offset, need to localize
# the parsed date to this offset first before aligning with the index's
Expand Down
20 changes: 20 additions & 0 deletions pandas/tests/indexes/datetimes/test_partial_slicing.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,26 @@ def test_slice_year(self):
expected = slice(3288, 3653)
assert result == expected

@pytest.mark.parametrize(
"partial_dtime",
[
"2019",
"2019Q4",
"Dec 2019",
"2019-12-31",
"2019-12-31 23",
"2019-12-31 23:59",
],
)
def test_slice_end_of_period_resolution(self, partial_dtime):
# GH#31064
dti = date_range("2019-12-31 23:59:55.999999999", periods=10, freq="s")

ser = pd.Series(range(10), index=dti)
result = ser[partial_dtime]
expected = ser.iloc[:5]
tm.assert_series_equal(result, expected)

def test_slice_quarter(self):
dti = date_range(freq="D", start=datetime(2000, 6, 1), periods=500)

Expand Down

0 comments on commit 65b23c2

Please sign in to comment.