diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 00dc8b2a4a13b..48de7771cd8d7 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -25,6 +25,7 @@ algos, lib, ) +from pandas._libs.arrays import NDArrayBacked from pandas._libs.tslibs import ( BaseOffset, IncompatibleFrequency, @@ -1906,6 +1907,81 @@ class TimelikeOps(DatetimeLikeArrayMixin): Common ops for TimedeltaIndex/DatetimeIndex, but not PeriodIndex. """ + _default_dtype: np.dtype + + def __init__(self, values, dtype=None, freq=lib.no_default, copy: bool = False): + values = extract_array(values, extract_numpy=True) + if isinstance(values, IntegerArray): + values = values.to_numpy("int64", na_value=iNaT) + + inferred_freq = getattr(values, "_freq", None) + explicit_none = freq is None + freq = freq if freq is not lib.no_default else None + + if isinstance(values, type(self)): + if explicit_none: + # don't inherit from values + pass + elif freq is None: + freq = values.freq + elif freq and values.freq: + freq = to_offset(freq) + freq, _ = validate_inferred_freq(freq, values.freq, False) + + if dtype is not None: + dtype = pandas_dtype(dtype) + if not is_dtype_equal(dtype, values.dtype): + # TODO: we only have tests for this for DTA, not TDA (2022-07-01) + raise TypeError( + f"dtype={dtype} does not match data dtype {values.dtype}" + ) + + dtype = values.dtype + values = values._ndarray + + elif dtype is None: + dtype = self._default_dtype + + if not isinstance(values, np.ndarray): + raise ValueError( + f"Unexpected type '{type(values).__name__}'. 'values' must be a " + f"{type(self).__name__}, ndarray, or Series or Index " + "containing one of those." + ) + if values.ndim not in [1, 2]: + raise ValueError("Only 1-dimensional input arrays are supported.") + + if values.dtype == "i8": + # for compat with datetime/timedelta/period shared methods, + # we can sometimes get here with int64 values. These represent + # nanosecond UTC (or tz-naive) unix timestamps + values = values.view(self._default_dtype) + + dtype = self._validate_dtype(values, dtype) + + if freq == "infer": + raise ValueError( + f"Frequency inference not allowed in {type(self).__name__}.__init__. " + "Use 'pd.array()' instead." + ) + + if copy: + values = values.copy() + if freq: + freq = to_offset(freq) + + NDArrayBacked.__init__(self, values=values, dtype=dtype) + self._freq = freq + + if inferred_freq is None and freq is not None: + type(self)._validate_frequency(self, freq) + + @classmethod + def _validate_dtype(cls, values, dtype): + raise AbstractMethodError(cls) + + # -------------------------------------------------------------- + @cache_readonly def _reso(self) -> int: return get_unit_from_dtype(self._ndarray.dtype) @@ -1917,6 +1993,8 @@ def _unit(self) -> str: # "ExtensionDtype"; expected "Union[DatetimeTZDtype, dtype[Any]]" return dtype_to_unit(self.dtype) # type: ignore[arg-type] + # -------------------------------------------------------------- + def __array_ufunc__(self, ufunc: np.ufunc, method: str, *inputs, **kwargs): if ( ufunc in [np.isnan, np.isinf, np.isfinite] diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index c019edea5eee0..ccb20b1dfaf1a 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -19,7 +19,6 @@ lib, tslib, ) -from pandas._libs.arrays import NDArrayBacked from pandas._libs.tslibs import ( BaseOffset, NaT, @@ -30,7 +29,6 @@ fields, get_resolution, get_unit_from_dtype, - iNaT, ints_to_pydatetime, is_date_array_normalized, is_unitless, @@ -72,9 +70,7 @@ from pandas.core.arrays import datetimelike as dtl from pandas.core.arrays._ranges import generate_regular_range -from pandas.core.arrays.integer import IntegerArray import pandas.core.common as com -from pandas.core.construction import extract_array from pandas.tseries.frequencies import get_period_alias from pandas.tseries.offsets import ( @@ -254,75 +250,14 @@ def _scalar_type(self) -> type[Timestamp]: _dtype: np.dtype | DatetimeTZDtype _freq = None + _default_dtype = DT64NS_DTYPE # used in TimeLikeOps.__init__ - def __init__( - self, values, dtype=None, freq=lib.no_default, copy: bool = False - ) -> None: - values = extract_array(values, extract_numpy=True) - if isinstance(values, IntegerArray): - values = values.to_numpy("int64", na_value=iNaT) - - inferred_freq = getattr(values, "_freq", None) - explicit_none = freq is None - freq = freq if freq is not lib.no_default else None - - if isinstance(values, type(self)): - if explicit_none: - # don't inherit from values - pass - elif freq is None: - freq = values.freq - elif freq and values.freq: - freq = to_offset(freq) - freq, _ = dtl.validate_inferred_freq(freq, values.freq, False) - - if dtype is not None: - dtype = pandas_dtype(dtype) - if not is_dtype_equal(dtype, values.dtype): - raise TypeError( - f"dtype={dtype} does not match data dtype {values.dtype}" - ) - - dtype = values.dtype - values = values._ndarray - - elif dtype is None: - dtype = DT64NS_DTYPE - - if not isinstance(values, np.ndarray): - raise ValueError( - f"Unexpected type '{type(values).__name__}'. 'values' must be a " - f"{type(self).__name__}, ndarray, or Series or Index " - "containing one of those." - ) - if values.ndim not in [1, 2]: - raise ValueError("Only 1-dimensional input arrays are supported.") - - if values.dtype == "i8": - # for compat with datetime/timedelta/period shared methods, - # we can sometimes get here with int64 values. These represent - # nanosecond UTC (or tz-naive) unix timestamps - values = values.view(DT64NS_DTYPE) - + @classmethod + def _validate_dtype(cls, values, dtype): + # used in TimeLikeOps.__init__ _validate_dt64_dtype(values.dtype) dtype = _validate_dt64_dtype(dtype) - - if freq == "infer": - raise ValueError( - f"Frequency inference not allowed in {type(self).__name__}.__init__. " - "Use 'pd.array()' instead." - ) - - if copy: - values = values.copy() - if freq: - freq = to_offset(freq) - - NDArrayBacked.__init__(self, values=values, dtype=dtype) - self._freq = freq - - if inferred_freq is None and freq is not None: - type(self)._validate_frequency(self, freq) + return dtype # error: Signature of "_simple_new" incompatible with supertype "NDArrayBacked" @classmethod diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index 0a43c93cb6c9b..fbd27aa026a37 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -12,7 +12,6 @@ lib, tslibs, ) -from pandas._libs.arrays import NDArrayBacked from pandas._libs.tslibs import ( BaseOffset, NaT, @@ -54,13 +53,9 @@ from pandas.core.dtypes.missing import isna from pandas.core import nanops -from pandas.core.arrays import ( - IntegerArray, - datetimelike as dtl, -) +from pandas.core.arrays import datetimelike as dtl from pandas.core.arrays._ranges import generate_regular_range import pandas.core.common as com -from pandas.core.construction import extract_array from pandas.core.ops.common import unpack_zerodim_and_defer if TYPE_CHECKING: @@ -171,64 +166,14 @@ def dtype(self) -> np.dtype: # type: ignore[override] # Constructors _freq = None + _default_dtype = TD64NS_DTYPE # used in TimeLikeOps.__init__ - def __init__( - self, values, dtype=TD64NS_DTYPE, freq=lib.no_default, copy: bool = False - ) -> None: - values = extract_array(values, extract_numpy=True) - if isinstance(values, IntegerArray): - values = values.to_numpy("int64", na_value=tslibs.iNaT) - - inferred_freq = getattr(values, "_freq", None) - explicit_none = freq is None - freq = freq if freq is not lib.no_default else None - - if isinstance(values, type(self)): - if explicit_none: - # don't inherit from values - pass - elif freq is None: - freq = values.freq - elif freq and values.freq: - freq = to_offset(freq) - freq, _ = dtl.validate_inferred_freq(freq, values.freq, False) - - values = values._ndarray - - if not isinstance(values, np.ndarray): - raise ValueError( - f"Unexpected type '{type(values).__name__}'. 'values' must be a " - f"{type(self).__name__}, ndarray, or Series or Index " - "containing one of those." - ) - if values.ndim not in [1, 2]: - raise ValueError("Only 1-dimensional input arrays are supported.") - - if values.dtype == "i8": - # for compat with datetime/timedelta/period shared methods, - # we can sometimes get here with int64 values. These represent - # nanosecond UTC (or tz-naive) unix timestamps - values = values.view(TD64NS_DTYPE) - + @classmethod + def _validate_dtype(cls, values, dtype): + # used in TimeLikeOps.__init__ _validate_td64_dtype(values.dtype) dtype = _validate_td64_dtype(dtype) - - if freq == "infer": - raise ValueError( - f"Frequency inference not allowed in {type(self).__name__}.__init__. " - "Use 'pd.array()' instead." - ) - - if copy: - values = values.copy() - if freq: - freq = to_offset(freq) - - NDArrayBacked.__init__(self, values=values, dtype=dtype) - self._freq = freq - - if inferred_freq is None and freq is not None: - type(self)._validate_frequency(self, freq) + return dtype # error: Signature of "_simple_new" incompatible with supertype "NDArrayBacked" @classmethod