Skip to content

Commit 8d2a57e

Browse files
authored
BUG: DTA/TDA/PA/Series/Index.view with datetimelike (#39788)
1 parent a2c4d99 commit 8d2a57e

File tree

4 files changed

+70
-1
lines changed

4 files changed

+70
-1
lines changed

doc/source/whatsnew/v1.3.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ Numeric
326326
Conversion
327327
^^^^^^^^^^
328328
- Bug in :meth:`Series.to_dict` with ``orient='records'`` now returns python native types (:issue:`25969`)
329+
- Bug in :meth:`Series.view` and :meth:`Index.view` when converting between datetime-like (``datetime64[ns]``, ``datetime64[ns, tz]``, ``timedelta64``, ``period``) dtypes (:issue:`39788`)
329330
-
330331
-
331332

pandas/core/arrays/datetimelike.py

+27-1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
)
4545
from pandas._libs.tslibs.timestamps import integer_op_not_supported
4646
from pandas._typing import (
47+
ArrayLike,
4748
DatetimeLikeScalar,
4849
Dtype,
4950
DtypeObj,
@@ -80,6 +81,10 @@
8081
is_unsigned_integer_dtype,
8182
pandas_dtype,
8283
)
84+
from pandas.core.dtypes.dtypes import (
85+
DatetimeTZDtype,
86+
PeriodDtype,
87+
)
8388
from pandas.core.dtypes.missing import (
8489
is_valid_na_for_dtype,
8590
isna,
@@ -430,9 +435,30 @@ def astype(self, dtype, copy=True):
430435
else:
431436
return np.asarray(self, dtype=dtype)
432437

433-
def view(self, dtype: Optional[Dtype] = None):
438+
def view(self, dtype: Optional[Dtype] = None) -> ArrayLike:
439+
# We handle datetime64, datetime64tz, timedelta64, and period
440+
# dtypes here. Everything else we pass through to the underlying
441+
# ndarray.
434442
if dtype is None or dtype is self.dtype:
435443
return type(self)(self._ndarray, dtype=self.dtype)
444+
445+
if isinstance(dtype, type):
446+
# we sometimes pass non-dtype objects, e.g np.ndarray;
447+
# pass those through to the underlying ndarray
448+
return self._ndarray.view(dtype)
449+
450+
dtype = pandas_dtype(dtype)
451+
if isinstance(dtype, (PeriodDtype, DatetimeTZDtype)):
452+
cls = dtype.construct_array_type()
453+
return cls._simple_new(self.asi8, dtype=dtype)
454+
elif dtype == "M8[ns]":
455+
from pandas.core.arrays import DatetimeArray
456+
457+
return DatetimeArray._simple_new(self.asi8, dtype=dtype)
458+
elif dtype == "m8[ns]":
459+
from pandas.core.arrays import TimedeltaArray
460+
461+
return TimedeltaArray._simple_new(self.asi8.view("m8[ns]"), dtype=dtype)
436462
return self._ndarray.view(dtype=dtype)
437463

438464
# ------------------------------------------------------------------

pandas/core/indexes/base.py

+17
Original file line numberDiff line numberDiff line change
@@ -793,6 +793,23 @@ def view(self, cls=None):
793793
# we need to see if we are subclassing an
794794
# index type here
795795
if cls is not None and not hasattr(cls, "_typ"):
796+
dtype = cls
797+
if isinstance(cls, str):
798+
dtype = pandas_dtype(cls)
799+
800+
if isinstance(dtype, (np.dtype, ExtensionDtype)) and needs_i8_conversion(
801+
dtype
802+
):
803+
if dtype.kind == "m" and dtype != "m8[ns]":
804+
# e.g. m8[s]
805+
return self._data.view(cls)
806+
807+
arr = self._data.view("i8")
808+
idx_cls = self._dtype_to_subclass(dtype)
809+
arr_cls = idx_cls._data_cls
810+
arr = arr_cls._simple_new(self._data.view("i8"), dtype=dtype)
811+
return idx_cls._simple_new(arr, name=self.name)
812+
796813
result = self._data.view(cls)
797814
else:
798815
result = self._view()

pandas/tests/series/methods/test_view.py

+25
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
import numpy as np
2+
import pytest
3+
14
from pandas import (
5+
Index,
26
Series,
7+
array as pd_array,
38
date_range,
49
)
510
import pandas._testing as tm
@@ -19,3 +24,23 @@ def test_view_tz(self):
1924
]
2025
)
2126
tm.assert_series_equal(result, expected)
27+
28+
@pytest.mark.parametrize(
29+
"first", ["m8[ns]", "M8[ns]", "M8[ns, US/Central]", "period[D]"]
30+
)
31+
@pytest.mark.parametrize(
32+
"second", ["m8[ns]", "M8[ns]", "M8[ns, US/Central]", "period[D]"]
33+
)
34+
@pytest.mark.parametrize("box", [Series, Index, pd_array])
35+
def test_view_between_datetimelike(self, first, second, box):
36+
37+
dti = date_range("2016-01-01", periods=3)
38+
39+
orig = box(dti)
40+
obj = orig.view(first)
41+
assert obj.dtype == first
42+
tm.assert_numpy_array_equal(np.asarray(obj.view("i8")), dti.asi8)
43+
44+
res = obj.view(second)
45+
assert res.dtype == second
46+
tm.assert_numpy_array_equal(np.asarray(obj.view("i8")), dti.asi8)

0 commit comments

Comments
 (0)