diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index 963acdc03d1f5..8a947e4505ec5 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -731,6 +731,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:`47266`) - Timedelta diff --git a/pandas/_libs/tslibs/conversion.pyx b/pandas/_libs/tslibs/conversion.pyx index 44335836dc2ee..fe558d5c58368 100644 --- a/pandas/_libs/tslibs/conversion.pyx +++ b/pandas/_libs/tslibs/conversion.pyx @@ -250,6 +250,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"]: + # 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) + 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) @@ -258,7 +264,11 @@ cdef _TSObject convert_to_tsobject(object ts, tzinfo tz, str unit, obj.value = NPY_NAT else: if unit in ["Y", "M"]: - if ts != int(ts): + if ts == int(ts): + # 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) + else: # GH#47267 it is clear that 2 "M" corresponds to 1970-02-01, # but not clear what 2.5 "M" corresponds to, so we will # disallow that case. diff --git a/pandas/tests/scalar/timestamp/test_constructors.py b/pandas/tests/scalar/timestamp/test_constructors.py index daf1730df489d..7b3647dc6cbef 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): + # GH#47266 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_float_not_round_with_YM_unit_deprecated(self): # GH#47267 avoid the conversions in cast_from-unit