From 37aeccd566ae099c4cd047026216ea678b4fd52a Mon Sep 17 00:00:00 2001 From: arthurlw Date: Wed, 16 Jul 2025 11:27:17 +0700 Subject: [PATCH 1/6] Fix inconsistency with DateOffset near DST --- pandas/_libs/tslibs/offsets.pyx | 4 +++- pandas/tests/tseries/offsets/test_offsets.py | 12 +++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 87214c3758d5c..c187318b37b4e 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -283,8 +283,10 @@ _relativedelta_kwds = {"years", "months", "weeks", "days", "year", "month", cdef _determine_offset(kwds): if not kwds: + from dateutil.relativedelta import relativedelta + # GH 45643/45890: (historically) defaults to 1 day - return timedelta(days=1), False + return relativedelta(days=1), True if "millisecond" in kwds: raise NotImplementedError( diff --git a/pandas/tests/tseries/offsets/test_offsets.py b/pandas/tests/tseries/offsets/test_offsets.py index 0b2e66a2b3a0d..9d8d42a70b18f 100644 --- a/pandas/tests/tseries/offsets/test_offsets.py +++ b/pandas/tests/tseries/offsets/test_offsets.py @@ -1095,9 +1095,11 @@ def test_dateoffset_misc(): @pytest.mark.parametrize("n", [-1, 1, 3]) def test_construct_int_arg_no_kwargs_assumed_days(n): + from dateutil.relativedelta import relativedelta + # GH 45890, 45643 offset = DateOffset(n) - assert offset._offset == timedelta(1) + assert offset._offset == relativedelta(days=1) result = Timestamp(2022, 1, 2) + offset expected = Timestamp(2022, 1, 2 + n) assert result == expected @@ -1227,3 +1229,11 @@ def test_is_yqm_start_end(): def test_multiply_dateoffset_typeerror(left, right): with pytest.raises(TypeError, match="Cannot multiply"): left * right + + +def test_dateoffset_days_vs_n_near_dst_transition(): + ts = Timestamp("2022-10-30", tz="Europe/Brussels") + + offset_days = ts + offsets.DateOffset(days=1) + offset_n = ts + offsets.DateOffset(1) + assert offset_days == offset_n From cbcd7750025050db1e290c40a25db4731aa7c3ae Mon Sep 17 00:00:00 2001 From: arthurlw Date: Thu, 17 Jul 2025 10:20:31 +0700 Subject: [PATCH 2/6] Updated with reviewer comments --- pandas/_libs/tslibs/offsets.pyx | 3 ++- pandas/tests/tseries/offsets/test_offsets.py | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index c187318b37b4e..e0717bfd1f43f 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -285,7 +285,8 @@ cdef _determine_offset(kwds): if not kwds: from dateutil.relativedelta import relativedelta - # GH 45643/45890: (historically) defaults to 1 day + # GH 45643, 45890: (historically) defaults to 1 day + # GH 61870: changed from timedelta to relativedelta return relativedelta(days=1), True if "millisecond" in kwds: diff --git a/pandas/tests/tseries/offsets/test_offsets.py b/pandas/tests/tseries/offsets/test_offsets.py index 9d8d42a70b18f..668c34fbbf165 100644 --- a/pandas/tests/tseries/offsets/test_offsets.py +++ b/pandas/tests/tseries/offsets/test_offsets.py @@ -9,6 +9,7 @@ timedelta, ) +from dateutil.relativedelta import relativedelta import numpy as np import pytest @@ -1095,9 +1096,7 @@ def test_dateoffset_misc(): @pytest.mark.parametrize("n", [-1, 1, 3]) def test_construct_int_arg_no_kwargs_assumed_days(n): - from dateutil.relativedelta import relativedelta - - # GH 45890, 45643 + # GH 45643, 45890, 61870 offset = DateOffset(n) assert offset._offset == relativedelta(days=1) result = Timestamp(2022, 1, 2) + offset From cd7c0d17d98348598331bcd8b04bcff0c41c13b5 Mon Sep 17 00:00:00 2001 From: arthurlw Date: Thu, 17 Jul 2025 10:24:01 +0700 Subject: [PATCH 3/6] Moved relativedelta imports to the top --- pandas/_libs/tslibs/offsets.pyx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index e0717bfd1f43f..d465defd0e971 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -18,6 +18,8 @@ from cpython.datetime cimport ( import warnings +from dateutil.relativedelta import relativedelta + import_datetime() import numpy as np @@ -283,8 +285,6 @@ _relativedelta_kwds = {"years", "months", "weeks", "days", "year", "month", cdef _determine_offset(kwds): if not kwds: - from dateutil.relativedelta import relativedelta - # GH 45643, 45890: (historically) defaults to 1 day # GH 61870: changed from timedelta to relativedelta return relativedelta(days=1), True @@ -328,8 +328,6 @@ cdef _determine_offset(kwds): kwds_no_nanos["microseconds"] = kwds_no_nanos.get("microseconds", 0) + micro if all(k in kwds_use_relativedelta for k in kwds_no_nanos): - from dateutil.relativedelta import relativedelta - return relativedelta(**kwds_no_nanos), True raise ValueError( From da7eb5888643d06012a0945320a50156420c995f Mon Sep 17 00:00:00 2001 From: Arthur Laureus Wigo <126365160+arthurlw@users.noreply.github.com> Date: Fri, 18 Jul 2025 10:40:23 +0700 Subject: [PATCH 4/6] Added GH reference --- pandas/tests/tseries/offsets/test_offsets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pandas/tests/tseries/offsets/test_offsets.py b/pandas/tests/tseries/offsets/test_offsets.py index 668c34fbbf165..70bd4de55a38b 100644 --- a/pandas/tests/tseries/offsets/test_offsets.py +++ b/pandas/tests/tseries/offsets/test_offsets.py @@ -1231,6 +1231,7 @@ def test_multiply_dateoffset_typeerror(left, right): def test_dateoffset_days_vs_n_near_dst_transition(): + # GH 61870 ts = Timestamp("2022-10-30", tz="Europe/Brussels") offset_days = ts + offsets.DateOffset(days=1) From e02dc29512bb19a0cc6ba2e9a6cd20273610f3d4 Mon Sep 17 00:00:00 2001 From: Arthur Laureus Wigo <126365160+arthurlw@users.noreply.github.com> Date: Fri, 18 Jul 2025 10:48:43 +0700 Subject: [PATCH 5/6] whatsnew --- doc/source/whatsnew/v3.0.0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 4e0e497379fa2..c70096ec45c92 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -716,6 +716,7 @@ Datetimelike Timedelta ^^^^^^^^^ - Accuracy improvement in :meth:`Timedelta.to_pytimedelta` to round microseconds consistently for large nanosecond based Timedelta (:issue:`57841`) +- Bug in :class:`pandas.tseries.offsets.DateOffset` where ``DateOffset(1)`` and ``DateOffset(days=1)`` returned different results near daylight saving time transitions. (:issue:`61862`) - Bug in :meth:`DataFrame.cumsum` which was raising ``IndexError`` if dtype is ``timedelta64[ns]`` (:issue:`57956`) Timezones From 91397e342dae09d8a4c6e487d6d676e037242be8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 18 Jul 2025 03:55:16 +0000 Subject: [PATCH 6/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- doc/source/whatsnew/v3.0.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index c078d97048f4c..7b2ecffa95ba1 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -720,8 +720,8 @@ Datetimelike Timedelta ^^^^^^^^^ - Accuracy improvement in :meth:`Timedelta.to_pytimedelta` to round microseconds consistently for large nanosecond based Timedelta (:issue:`57841`) -- Bug in :class:`pandas.tseries.offsets.DateOffset` where ``DateOffset(1)`` and ``DateOffset(days=1)`` returned different results near daylight saving time transitions. (:issue:`61862`) - Bug in :class:`Timedelta` constructor failing to raise when passed an invalid keyword (:issue:`53801`) +- Bug in :class:`pandas.tseries.offsets.DateOffset` where ``DateOffset(1)`` and ``DateOffset(days=1)`` returned different results near daylight saving time transitions. (:issue:`61862`) - Bug in :meth:`DataFrame.cumsum` which was raising ``IndexError`` if dtype is ``timedelta64[ns]`` (:issue:`57956`) Timezones