From 0d8b033024b85c094b2da5558e2c82045f3020a7 Mon Sep 17 00:00:00 2001 From: Brock Date: Mon, 6 Jun 2022 19:25:47 -0700 Subject: [PATCH 1/3] BUG: Timestamp with unit=Y or unit=M --- doc/source/whatsnew/v1.5.0.rst | 1 + pandas/_libs/tslibs/conversion.pyx | 12 ++++++++++++ pandas/tests/scalar/timestamp/test_constructors.py | 13 +++++++++++++ 3 files changed, 26 insertions(+) diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index 8a7ad077c2a90..5b053872f2ec6 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -726,6 +726,7 @@ Datetimelike - Bug in :meth:`SeriesGroupBy.value_counts` index when passing categorical column (:issue:`44324`) - Bug in :meth:`DatetimeIndex.tz_localize` localizing to UTC failing to make a copy of the underlying data (:issue:`46460`) - Bug in :meth:`DatetimeIndex.resolution` incorrectly returning "day" instead of "nanosecond" for nanosecond-resolution indexes (:issue:`46903`) +- Bug in :class:`Timestamp` with an integer or float value and ``unit="Y"`` or ``unit="M"`` giving slightly-wrong results (:issue:`??`) - Timedelta diff --git a/pandas/_libs/tslibs/conversion.pyx b/pandas/_libs/tslibs/conversion.pyx index 808f750c18c9d..f2b1439f7024b 100644 --- a/pandas/_libs/tslibs/conversion.pyx +++ b/pandas/_libs/tslibs/conversion.pyx @@ -248,6 +248,12 @@ cdef _TSObject convert_to_tsobject(object ts, tzinfo tz, str unit, if ts == NPY_NAT: obj.value = NPY_NAT else: + if unit in ["Y", "M"]: + # cast_from_unit leads to weird results e.g. with "Y" and 150 + # we'd get 2120-01-01 09:00:00 + ts = np.datetime64(ts, unit.upper()) + return convert_to_tsobject(ts, tz, None, False, False) + ts = ts * cast_from_unit(None, unit) obj.value = ts dt64_to_dtstruct(ts, &obj.dts) @@ -255,6 +261,12 @@ cdef _TSObject convert_to_tsobject(object ts, tzinfo tz, str unit, if ts != ts or ts == NPY_NAT: obj.value = NPY_NAT else: + if unit in ["Y", "M"]: + if ts == int(ts): + # Avoid cast_from_unit, which would give weird results e.g. + # with "Y" and 150.0 we'd get 2120-01-01 09:00:00 + return convert_to_tsobject(int(ts), tz, unit, False, False) + ts = cast_from_unit(ts, unit) obj.value = ts dt64_to_dtstruct(ts, &obj.dts) diff --git a/pandas/tests/scalar/timestamp/test_constructors.py b/pandas/tests/scalar/timestamp/test_constructors.py index a5641473c4d52..7a1ca55c20c56 100644 --- a/pandas/tests/scalar/timestamp/test_constructors.py +++ b/pandas/tests/scalar/timestamp/test_constructors.py @@ -25,6 +25,19 @@ class TestTimestampConstructors: + @pytest.mark.parametrize("typ", [int, float]) + def test_constructor_int_float_with_YM_unit(self, typ): + # avoid the conversions in cast_from_unit + val = typ(150) + + ts = Timestamp(val, unit="Y") + expected = Timestamp("2120-01-01") + assert ts == expected + + ts = Timestamp(val, unit="M") + expected = Timestamp("1982-07-01") + assert ts == expected + def test_constructor_datetime64_with_tz(self): # GH#42288, GH#24559 dt = np.datetime64("1970-01-01 05:00:00") From c5ebf0f16c555ab3ac340a1222903159f4494097 Mon Sep 17 00:00:00 2001 From: Brock Date: Mon, 6 Jun 2022 19:30:49 -0700 Subject: [PATCH 2/3] GH refs --- doc/source/whatsnew/v1.5.0.rst | 2 +- pandas/_libs/tslibs/conversion.pyx | 8 ++++---- pandas/tests/scalar/timestamp/test_constructors.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index 5b053872f2ec6..e130b6b5ed547 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -726,7 +726,7 @@ Datetimelike - Bug in :meth:`SeriesGroupBy.value_counts` index when passing categorical column (:issue:`44324`) - Bug in :meth:`DatetimeIndex.tz_localize` localizing to UTC failing to make a copy of the underlying data (:issue:`46460`) - Bug in :meth:`DatetimeIndex.resolution` incorrectly returning "day" instead of "nanosecond" for nanosecond-resolution indexes (:issue:`46903`) -- Bug in :class:`Timestamp` with an integer or float value and ``unit="Y"`` or ``unit="M"`` giving slightly-wrong results (:issue:`??`) +- Bug in :class:`Timestamp` with an integer or float value and ``unit="Y"`` or ``unit="M"`` giving slightly-wrong results (:issue:`47266`) - Timedelta diff --git a/pandas/_libs/tslibs/conversion.pyx b/pandas/_libs/tslibs/conversion.pyx index f2b1439f7024b..b3f52036105b4 100644 --- a/pandas/_libs/tslibs/conversion.pyx +++ b/pandas/_libs/tslibs/conversion.pyx @@ -249,8 +249,8 @@ cdef _TSObject convert_to_tsobject(object ts, tzinfo tz, str unit, obj.value = NPY_NAT else: if unit in ["Y", "M"]: - # cast_from_unit leads to weird results e.g. with "Y" and 150 - # we'd get 2120-01-01 09:00:00 + # GH#47266 cast_from_unit leads to weird results e.g. with "Y" + # and 150 we'd get 2120-01-01 09:00:00 ts = np.datetime64(ts, unit.upper()) return convert_to_tsobject(ts, tz, None, False, False) @@ -263,8 +263,8 @@ cdef _TSObject convert_to_tsobject(object ts, tzinfo tz, str unit, else: if unit in ["Y", "M"]: if ts == int(ts): - # Avoid cast_from_unit, which would give weird results e.g. - # with "Y" and 150.0 we'd get 2120-01-01 09:00:00 + # GH#47266 Avoid cast_from_unit, which would give weird results + # e.g. with "Y" and 150.0 we'd get 2120-01-01 09:00:00 return convert_to_tsobject(int(ts), tz, unit, False, False) ts = cast_from_unit(ts, unit) diff --git a/pandas/tests/scalar/timestamp/test_constructors.py b/pandas/tests/scalar/timestamp/test_constructors.py index 7a1ca55c20c56..bc7e503184d30 100644 --- a/pandas/tests/scalar/timestamp/test_constructors.py +++ b/pandas/tests/scalar/timestamp/test_constructors.py @@ -27,7 +27,7 @@ class TestTimestampConstructors: @pytest.mark.parametrize("typ", [int, float]) def test_constructor_int_float_with_YM_unit(self, typ): - # avoid the conversions in cast_from_unit + # GH#47266 avoid the conversions in cast_from_unit val = typ(150) ts = Timestamp(val, unit="Y") From b40d31b7e274acaa892c580e82cfb05ab6e4646c Mon Sep 17 00:00:00 2001 From: Brock Date: Tue, 7 Jun 2022 12:18:11 -0700 Subject: [PATCH 3/3] remove unnecessary upper --- pandas/_libs/tslibs/conversion.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/_libs/tslibs/conversion.pyx b/pandas/_libs/tslibs/conversion.pyx index 407099fecf723..fe558d5c58368 100644 --- a/pandas/_libs/tslibs/conversion.pyx +++ b/pandas/_libs/tslibs/conversion.pyx @@ -253,7 +253,7 @@ cdef _TSObject convert_to_tsobject(object ts, tzinfo tz, str unit, if unit in ["Y", "M"]: # GH#47266 cast_from_unit leads to weird results e.g. with "Y" # and 150 we'd get 2120-01-01 09:00:00 - ts = np.datetime64(ts, unit.upper()) + ts = np.datetime64(ts, unit) return convert_to_tsobject(ts, tz, None, False, False) ts = ts * cast_from_unit(None, unit)