Skip to content
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion pandas/core/arrays/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
NaT,
NaTType,
Resolution,
Timedelta,
Timestamp,
astype_overflowsafe,
fields,
Expand Down Expand Up @@ -70,6 +71,7 @@

from pandas.tseries.frequencies import get_period_alias
from pandas.tseries.offsets import (
DateOffset,
Day,
Tick,
)
Expand All @@ -93,7 +95,6 @@

from pandas import (
DataFrame,
Timedelta,
)
from pandas.core.arrays import PeriodArray

Expand Down Expand Up @@ -817,7 +818,34 @@ def _add_offset(self, offset: BaseOffset) -> Self:
result = type(self)._from_sequence(res_values, dtype=self.dtype)

else:
units = [
"ns",
"us",
"ms",
"s",
]
res_unit = self.unit
if type(offset) is DateOffset:
nano = offset.kwds.get("nanoseconds", 0)
micro = offset.kwds.get("microseconds", 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a bit tricky. if the attribute exists and is zero, we still want the high resolution. its only if the attribute doesn't exist that we want the lower unit

if nano:
res_unit = "ns"
elif micro and self.unit != "ns":
res_unit = "us"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

side-note for another PR: might be worth defining a unit property on DateOffset since i think this recently came up in date_range too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing that out. would it make sense to open an issue for it or would you prefer I handle it directly in a follow up PR?

if (
hasattr(offset, "offset")
and offset.offset is not None
and not isinstance(offset, Tick)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think the Tick check is redundant here?

):
offset_td = Timedelta(offset.offset)
if offset_td.value != 0:
offset_unit = offset_td.unit
idx_self = units.index(self.unit)
idx_offset = units.index(offset_unit)
res_unit = units[min(idx_self, idx_offset)]
result = type(self)._simple_new(res_values, dtype=res_values.dtype)
result = result.as_unit(res_unit)

if offset.normalize:
result = result.normalize()
result._freq = None
Expand Down
20 changes: 20 additions & 0 deletions pandas/tests/arrays/test_datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -844,3 +844,23 @@ def test_factorize_sort_without_freq():
tda = dta - dta[0]
with pytest.raises(NotImplementedError, match=msg):
tda.factorize(sort=True)


@pytest.mark.filterwarnings(
"ignore:Non-vectorized DateOffset being applied to Series or DatetimeIndex"
)
def test_dt64_non_nano_offset_no_rounding():
# GH#56586
dti = pd.date_range("2016-01-01", periods=3, unit="s")
offset = pd.offsets.CustomBusinessDay(offset=pd.Timedelta("1ms"))
result = dti + offset

assert result.dtype == np.dtype("datetime64[ms]")
expected = pd.DatetimeIndex(
[
pd.Timestamp("2016-01-02 00:00:00.001"),
pd.Timestamp("2016-01-03 00:00:00.001"),
pd.Timestamp("2016-01-04 00:00:00.001"),
]
)
tm.assert_index_equal(result, expected)
Loading