diff --git a/pandas/core/dtypes/cast.py b/pandas/core/dtypes/cast.py index abcc60a15c641..3ff785edf2da1 100644 --- a/pandas/core/dtypes/cast.py +++ b/pandas/core/dtypes/cast.py @@ -30,7 +30,6 @@ conversion, iNaT, ints_to_pydatetime, - ints_to_pytimedelta, ) from pandas._libs.tslibs.timezones import tz_compare from pandas._typing import AnyArrayLike, ArrayLike, Dtype, DtypeObj, Scalar @@ -987,15 +986,20 @@ def astype_nansafe( elif not isinstance(dtype, np.dtype): raise ValueError("dtype must be np.dtype or ExtensionDtype") + if arr.dtype.kind in ["m", "M"] and dtype == object: + # make sure we wrap in Timedelta/Timestamp, not timedelta/datetime + from pandas.core.construction import ensure_wrapped_if_datetimelike + + arr = ensure_wrapped_if_datetimelike(arr) + return arr.astype(dtype, copy=copy) + if issubclass(dtype.type, str): return lib.ensure_string_array( arr.ravel(), skipna=skipna, convert_na_value=False ).reshape(arr.shape) elif is_datetime64_dtype(arr): - if is_object_dtype(dtype): - return ints_to_pydatetime(arr.view(np.int64)) - elif dtype == np.int64: + if dtype == np.int64: if isna(arr).any(): raise ValueError("Cannot convert NaT values to integer") return arr.view(dtype) @@ -1007,9 +1011,7 @@ def astype_nansafe( raise TypeError(f"cannot astype a datetimelike from [{arr.dtype}] to [{dtype}]") elif is_timedelta64_dtype(arr): - if is_object_dtype(dtype): - return ints_to_pytimedelta(arr.view(np.int64)) - elif dtype == np.int64: + if dtype == np.int64: if isna(arr).any(): raise ValueError("Cannot convert NaT values to integer") return arr.view(dtype) diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py index 2630c07814bb2..b4e84779afa60 100644 --- a/pandas/core/internals/blocks.py +++ b/pandas/core/internals/blocks.py @@ -677,16 +677,6 @@ def _astype(self, dtype: DtypeObj, copy: bool) -> ArrayLike: # StringDtype, this matches arr.astype(dtype), xref GH#36153 values = arr._format_native_types(na_rep="NaT") - elif is_object_dtype(dtype): - if values.dtype.kind in ["m", "M"]: - # Wrap in Timedelta/Timestamp - arr = pd_array(values) - values = arr.astype(object) - else: - values = values.astype(object) - # We still need to go through astype_nansafe for - # e.g. dtype = Sparse[object, 0] - values = astype_nansafe(values, dtype, copy=True) return values diff --git a/pandas/tests/dtypes/test_common.py b/pandas/tests/dtypes/test_common.py index 0d0601aa542b4..7e8ce19d03ccd 100644 --- a/pandas/tests/dtypes/test_common.py +++ b/pandas/tests/dtypes/test_common.py @@ -754,6 +754,19 @@ def test_astype_object_preserves_datetime_na(from_type): assert isna(result)[0] +@pytest.mark.parametrize("from_dtype", ["datetime64[ns]", "timedelta64[ns]"]) +def test_astype_object_boxes_timestamps_timedeltas(from_dtype): + arr = np.array([3600], dtype=from_dtype) + result = astype_nansafe(arr, dtype=np.dtype("object")) + + assert result.dtype == object + + if from_dtype == "datetime64[ns]": + assert isinstance(result[0], pd.Timestamp) + else: + assert isinstance(result[0], pd.Timedelta) + + def test_validate_allhashable(): assert com.validate_all_hashable(1, "a") is None diff --git a/pandas/tests/series/test_constructors.py b/pandas/tests/series/test_constructors.py index 5b13091470b09..8d99467e5285a 100644 --- a/pandas/tests/series/test_constructors.py +++ b/pandas/tests/series/test_constructors.py @@ -24,6 +24,7 @@ Period, RangeIndex, Series, + Timedelta, Timestamp, date_range, isna, @@ -1513,6 +1514,22 @@ def test_constructor_sparse_datetime64(self, values): expected = Series(arr) tm.assert_series_equal(result, expected) + def test_constructor_datetimelike_to_sparse_object(self): + arr = np.arange(3, dtype="i8").view("M8[D]").astype("M8[ns]") + arr2 = arr.view("m8[ns]") + + ser = Series(arr, dtype="Sparse[object]") + assert isinstance(ser[0], Timestamp) + + ser = Series(arr).astype("Sparse[object]") + assert isinstance(ser[0], Timestamp) + + ser2 = Series(arr2, dtype="Sparse[object]") + assert isinstance(ser2[0], Timedelta) + + ser2 = Series(arr2).astype("Sparse[object]") + assert isinstance(ser2[0], Timedelta) + def test_construction_from_ordered_collection(self): # https://github.com/pandas-dev/pandas/issues/36044 result = Series({"a": 1, "b": 2}.keys())