From f3c46cd0899d5e11e0602798d9390c90e51e9ba7 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Thu, 10 Nov 2022 12:56:42 -0800 Subject: [PATCH] API: make Timestamp/Timedelta _as_unit public as_unit (#48819) * API: make Timestamp/Timedelta _as_unit public as_unit * update test * update test * update tests * fix pyi typo * fixup * fixup --- doc/source/reference/arrays.rst | 4 ++ pandas/_libs/tslib.pyx | 4 +- pandas/_libs/tslibs/nattype.pyi | 1 + pandas/_libs/tslibs/nattype.pyx | 16 +++++++ pandas/_libs/tslibs/timedeltas.pyi | 4 +- pandas/_libs/tslibs/timedeltas.pyx | 21 ++++++++- pandas/_libs/tslibs/timestamps.pyi | 4 +- pandas/_libs/tslibs/timestamps.pyx | 17 ++++++- pandas/core/arrays/datetimelike.py | 20 ++++---- pandas/core/arrays/datetimes.py | 10 ++-- pandas/core/arrays/timedeltas.py | 6 +-- pandas/core/dtypes/common.py | 2 +- pandas/core/dtypes/dtypes.py | 4 +- pandas/core/window/ewm.py | 2 +- pandas/tests/arrays/test_datetimes.py | 20 ++++---- pandas/tests/arrays/test_timedeltas.py | 2 +- .../tests/indexes/datetimes/test_indexing.py | 4 +- .../tests/indexes/timedeltas/test_indexing.py | 4 +- pandas/tests/io/json/test_pandas.py | 2 +- pandas/tests/scalar/test_nat.py | 4 +- .../scalar/timedelta/test_constructors.py | 4 +- .../tests/scalar/timedelta/test_timedelta.py | 30 ++++++------ .../tests/scalar/timestamp/test_timestamp.py | 46 +++++++++---------- .../tests/scalar/timestamp/test_timezones.py | 8 ++-- .../tests/scalar/timestamp/test_unary_ops.py | 14 +++--- pandas/tests/series/test_constructors.py | 2 +- pandas/tests/tseries/offsets/test_offsets.py | 4 +- 27 files changed, 156 insertions(+), 103 deletions(-) diff --git a/doc/source/reference/arrays.rst b/doc/source/reference/arrays.rst index 33a611b15675d..5b41de4e12e6f 100644 --- a/doc/source/reference/arrays.rst +++ b/doc/source/reference/arrays.rst @@ -139,6 +139,7 @@ Properties Timestamp.second Timestamp.tz Timestamp.tzinfo + Timestamp.unit Timestamp.value Timestamp.week Timestamp.weekofyear @@ -149,6 +150,7 @@ Methods .. autosummary:: :toctree: api/ + Timestamp.as_unit Timestamp.astimezone Timestamp.ceil Timestamp.combine @@ -242,6 +244,7 @@ Properties Timedelta.nanoseconds Timedelta.resolution Timedelta.seconds + Timedelta.unit Timedelta.value Timedelta.view @@ -250,6 +253,7 @@ Methods .. autosummary:: :toctree: api/ + Timedelta.as_unit Timedelta.ceil Timedelta.floor Timedelta.isoformat diff --git a/pandas/_libs/tslib.pyx b/pandas/_libs/tslib.pyx index 6d6e90673f030..d7c4c022a2556 100644 --- a/pandas/_libs/tslib.pyx +++ b/pandas/_libs/tslib.pyx @@ -551,7 +551,7 @@ cpdef array_to_datetime( raise ValueError('Cannot mix tz-aware with ' 'tz-naive values') if isinstance(val, _Timestamp): - iresult[i] = val._as_unit("ns").value + iresult[i] = val.as_unit("ns").value else: iresult[i] = pydatetime_to_dt64(val, &dts) check_dts_bounds(&dts) @@ -906,7 +906,7 @@ def array_to_datetime_with_tz(ndarray values, tzinfo tz): else: # datetime64, tznaive pydatetime, int, float ts = ts.tz_localize(tz) - ts = ts._as_unit("ns") + ts = ts.as_unit("ns") ival = ts.value # Analogous to: result[i] = ival diff --git a/pandas/_libs/tslibs/nattype.pyi b/pandas/_libs/tslibs/nattype.pyi index e9ae46cee7aec..72f55bb50895a 100644 --- a/pandas/_libs/tslibs/nattype.pyi +++ b/pandas/_libs/tslibs/nattype.pyi @@ -127,3 +127,4 @@ class NaTType: __le__: _NatComparison __gt__: _NatComparison __ge__: _NatComparison + def as_unit(self, unit: str, round_ok: bool = ...) -> NaTType: ... diff --git a/pandas/_libs/tslibs/nattype.pyx b/pandas/_libs/tslibs/nattype.pyx index e9fb40bbcdf85..dcb7358d8e69a 100644 --- a/pandas/_libs/tslibs/nattype.pyx +++ b/pandas/_libs/tslibs/nattype.pyx @@ -1195,6 +1195,22 @@ default 'raise' def tzinfo(self) -> None: return None + def as_unit(self, str unit, bint round_ok=True) -> "NaTType": + """ + Convert the underlying int64 representaton to the given unit. + + Parameters + ---------- + unit : {"ns", "us", "ms", "s"} + round_ok : bool, default True + If False and the conversion requires rounding, raise. + + Returns + ------- + Timestamp + """ + return c_NaT + c_NaT = NaTType() # C-visible NaT = c_NaT # Python-visible diff --git a/pandas/_libs/tslibs/timedeltas.pyi b/pandas/_libs/tslibs/timedeltas.pyi index ef3cd6df167f4..f41bea11985f2 100644 --- a/pandas/_libs/tslibs/timedeltas.pyi +++ b/pandas/_libs/tslibs/timedeltas.pyi @@ -152,5 +152,5 @@ class Timedelta(timedelta): def to_numpy(self) -> np.timedelta64: ... def view(self, dtype: npt.DTypeLike = ...) -> object: ... @property - def _unit(self) -> str: ... - def _as_unit(self, unit: str, round_ok: bool = ...) -> Timedelta: ... + def unit(self) -> str: ... + def as_unit(self, unit: str, round_ok: bool = ...) -> Timedelta: ... diff --git a/pandas/_libs/tslibs/timedeltas.pyx b/pandas/_libs/tslibs/timedeltas.pyx index 83b4f34bfb70b..071cfb7cf541a 100644 --- a/pandas/_libs/tslibs/timedeltas.pyx +++ b/pandas/_libs/tslibs/timedeltas.pyx @@ -339,7 +339,7 @@ cdef convert_to_timedelta64(object ts, str unit): elif isinstance(ts, _Timedelta): # already in the proper format if ts._creso != NPY_FR_ns: - ts = ts._as_unit("ns").asm8 + ts = ts.as_unit("ns").asm8 else: ts = np.timedelta64(ts.value, "ns") elif is_timedelta64_object(ts): @@ -1081,6 +1081,10 @@ cdef class _Timedelta(timedelta): # TODO: add nanos/1e9? return self.days * 24 * 3600 + self.seconds + self.microseconds / 1_000_000 + @property + def unit(self) -> str: + return npy_unit_to_abbrev(self._creso) + def __hash__(_Timedelta self): if self._has_ns(): # Note: this does *not* satisfy the invariance @@ -1500,7 +1504,20 @@ cdef class _Timedelta(timedelta): # exposing as classmethod for testing return _timedelta_from_value_and_reso(value, reso) - def _as_unit(self, str unit, bint round_ok=True): + def as_unit(self, str unit, bint round_ok=True): + """ + Convert the underlying int64 representaton to the given unit. + + Parameters + ---------- + unit : {"ns", "us", "ms", "s"} + round_ok : bool, default True + If False and the conversion requires rounding, raise. + + Returns + ------- + Timedelta + """ dtype = np.dtype(f"m8[{unit}]") reso = get_unit_from_dtype(dtype) return self._as_creso(reso, round_ok=round_ok) diff --git a/pandas/_libs/tslibs/timestamps.pyi b/pandas/_libs/tslibs/timestamps.pyi index 77f02741aae48..3f795a2d08959 100644 --- a/pandas/_libs/tslibs/timestamps.pyi +++ b/pandas/_libs/tslibs/timestamps.pyi @@ -220,5 +220,5 @@ class Timestamp(datetime): @property def daysinmonth(self) -> int: ... @property - def _unit(self) -> str: ... - def _as_unit(self, unit: str, round_ok: bool = ...) -> Timestamp: ... + def unit(self) -> str: ... + def as_unit(self, unit: str, round_ok: bool = ...) -> Timestamp: ... diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index ac8a6738a816c..b0208f9ca3296 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -233,7 +233,7 @@ cdef class _Timestamp(ABCTimestamp): resolution = MinMaxReso("resolution") # GH#21336, GH#21365 @property - def _unit(self) -> str: + def unit(self) -> str: """ The abbreviation associated with self._creso. """ @@ -993,7 +993,20 @@ cdef class _Timestamp(ABCTimestamp): value = convert_reso(self.value, self._creso, reso, round_ok=round_ok) return type(self)._from_value_and_reso(value, reso=reso, tz=self.tzinfo) - def _as_unit(self, str unit, bint round_ok=True): + def as_unit(self, str unit, bint round_ok=True): + """ + Convert the underlying int64 representaton to the given unit. + + Parameters + ---------- + unit : {"ns", "us", "ms", "s"} + round_ok : bool, default True + If False and the conversion requires rounding, raise. + + Returns + ------- + Timestamp + """ dtype = np.dtype(f"M8[{unit}]") reso = get_unit_from_dtype(dtype) try: diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index b1d9fba22b484..b36eeab70726e 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -816,7 +816,7 @@ def isin(self, values) -> npt.NDArray[np.bool_]: if self.dtype.kind in ["m", "M"]: self = cast("DatetimeArray | TimedeltaArray", self) - values = values._as_unit(self._unit) + values = values.as_unit(self.unit) try: self._check_compatible_with(values) @@ -1116,7 +1116,7 @@ def _add_datetimelike_scalar(self, other) -> DatetimeArray: # i.e. np.datetime64("NaT") # In this case we specifically interpret NaT as a datetime, not # the timedelta interpretation we would get by returning self + NaT - result = self._ndarray + NaT.to_datetime64().astype(f"M8[{self._unit}]") + result = self._ndarray + NaT.to_datetime64().astype(f"M8[{self.unit}]") # Preserve our resolution return DatetimeArray._simple_new(result, dtype=result.dtype) @@ -1128,10 +1128,10 @@ def _add_datetimelike_scalar(self, other) -> DatetimeArray: result = checked_add_with_arr( self.asi8, other_i8, arr_mask=self._isnan, b_mask=o_mask ) - res_values = result.view(f"M8[{self._unit}]") + res_values = result.view(f"M8[{self.unit}]") - dtype = tz_to_dtype(tz=other.tz, unit=self._unit) - res_values = result.view(f"M8[{self._unit}]") + dtype = tz_to_dtype(tz=other.tz, unit=self.unit) + res_values = result.view(f"M8[{self.unit}]") new_freq = self._get_arithmetic_result_freq(other) return DatetimeArray._simple_new(res_values, dtype=dtype, freq=new_freq) @@ -1191,7 +1191,7 @@ def _sub_datetimelike(self, other: Timestamp | DatetimeArray) -> TimedeltaArray: res_values = checked_add_with_arr( self.asi8, -other_i8, arr_mask=self._isnan, b_mask=o_mask ) - res_m8 = res_values.view(f"timedelta64[{self._unit}]") + res_m8 = res_values.view(f"timedelta64[{self.unit}]") new_freq = self._get_arithmetic_result_freq(other) return TimedeltaArray._simple_new(res_m8, dtype=res_m8.dtype, freq=new_freq) @@ -1989,13 +1989,13 @@ def _creso(self) -> int: return get_unit_from_dtype(self._ndarray.dtype) @cache_readonly - def _unit(self) -> str: + def unit(self) -> str: # e.g. "ns", "us", "ms" # error: Argument 1 to "dtype_to_unit" has incompatible type # "ExtensionDtype"; expected "Union[DatetimeTZDtype, dtype[Any]]" return dtype_to_unit(self.dtype) # type: ignore[arg-type] - def _as_unit(self: TimelikeOpsT, unit: str) -> TimelikeOpsT: + def as_unit(self: TimelikeOpsT, unit: str) -> TimelikeOpsT: dtype = np.dtype(f"{self.dtype.kind}8[{unit}]") new_values = astype_overflowsafe(self._ndarray, dtype, round_ok=True) @@ -2017,9 +2017,9 @@ def _ensure_matching_resos(self, other): if self._creso != other._creso: # Just as with Timestamp/Timedelta, we cast to the higher resolution if self._creso < other._creso: - self = self._as_unit(other._unit) + self = self.as_unit(other.unit) else: - other = other._as_unit(self._unit) + other = other.as_unit(self.unit) return self, other # -------------------------------------------------------------- diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 6d31d0086d84b..d0a932ec378b9 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -351,9 +351,9 @@ def _from_sequence_not_strict( data_unit = np.datetime_data(subarr.dtype)[0] data_dtype = tz_to_dtype(tz, data_unit) result = cls._simple_new(subarr, freq=freq, dtype=data_dtype) - if unit is not None and unit != result._unit: + if unit is not None and unit != result.unit: # If unit was specified in user-passed dtype, cast to it here - result = result._as_unit(unit) + result = result.as_unit(unit) if inferred_freq is None and freq is not None: # this condition precludes `freq_infer` @@ -843,7 +843,7 @@ def tz_convert(self, tz) -> DatetimeArray: ) # No conversion since timestamps are all UTC to begin with - dtype = tz_to_dtype(tz, unit=self._unit) + dtype = tz_to_dtype(tz, unit=self.unit) return self._simple_new(self._ndarray, dtype=dtype, freq=self.freq) @dtl.ravel_compat @@ -1018,8 +1018,8 @@ def tz_localize( nonexistent=nonexistent, creso=self._creso, ) - new_dates = new_dates.view(f"M8[{self._unit}]") - dtype = tz_to_dtype(tz, unit=self._unit) + new_dates = new_dates.view(f"M8[{self.unit}]") + dtype = tz_to_dtype(tz, unit=self.unit) freq = None if timezones.is_utc(tz) or (len(self) == 1 and not isna(new_dates[0])): diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index 65996b1df5e9a..fe7cade1711d0 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -268,10 +268,10 @@ def _generate_range(cls, start, end, periods, freq, closed=None): ) if start is not None: - start = Timedelta(start)._as_unit("ns") + start = Timedelta(start).as_unit("ns") if end is not None: - end = Timedelta(end)._as_unit("ns") + end = Timedelta(end).as_unit("ns") left_closed, right_closed = validate_endpoints(closed) @@ -298,7 +298,7 @@ def _unbox_scalar(self, value) -> np.timedelta64: if value is NaT: return np.timedelta64(value.value, "ns") else: - return value._as_unit(self._unit).asm8 + return value.as_unit(self.unit).asm8 def _scalar_from_string(self, value) -> Timedelta | NaTType: return Timedelta(value) diff --git a/pandas/core/dtypes/common.py b/pandas/core/dtypes/common.py index e9d3721bbb5f5..6a366fa53e957 100644 --- a/pandas/core/dtypes/common.py +++ b/pandas/core/dtypes/common.py @@ -931,7 +931,7 @@ def is_datetime64_ns_dtype(arr_or_dtype) -> bool: else: return False return tipo == DT64NS_DTYPE or ( - isinstance(tipo, DatetimeTZDtype) and tipo._unit == "ns" + isinstance(tipo, DatetimeTZDtype) and tipo.unit == "ns" ) diff --git a/pandas/core/dtypes/dtypes.py b/pandas/core/dtypes/dtypes.py index e46e081c57d8a..15fcfe80b1915 100644 --- a/pandas/core/dtypes/dtypes.py +++ b/pandas/core/dtypes/dtypes.py @@ -676,7 +676,7 @@ def na_value(self) -> NaTType: # error: Signature of "str" incompatible with supertype "PandasExtensionDtype" @cache_readonly def str(self) -> str: # type: ignore[override] - return f"|M8[{self._unit}]" + return f"|M8[{self.unit}]" def __init__(self, unit: str_type | DatetimeTZDtype = "ns", tz=None) -> None: if isinstance(unit, DatetimeTZDtype): @@ -720,7 +720,7 @@ def _creso(self) -> int: "ms": dtypes.NpyDatetimeUnit.NPY_FR_ms, "us": dtypes.NpyDatetimeUnit.NPY_FR_us, "ns": dtypes.NpyDatetimeUnit.NPY_FR_ns, - }[self._unit] + }[self.unit] return reso.value @property diff --git a/pandas/core/window/ewm.py b/pandas/core/window/ewm.py index c5c401d415ad0..b53e9fc3c55ec 100644 --- a/pandas/core/window/ewm.py +++ b/pandas/core/window/ewm.py @@ -123,7 +123,7 @@ def _calculate_deltas( """ _times = np.asarray(times.view(np.int64), dtype=np.float64) # TODO: generalize to non-nano? - _halflife = float(Timedelta(halflife)._as_unit("ns").value) + _halflife = float(Timedelta(halflife).as_unit("ns").value) return np.diff(_times) / _halflife diff --git a/pandas/tests/arrays/test_datetimes.py b/pandas/tests/arrays/test_datetimes.py index 564194ed4a9d3..166362a9a8c30 100644 --- a/pandas/tests/arrays/test_datetimes.py +++ b/pandas/tests/arrays/test_datetimes.py @@ -215,12 +215,12 @@ def test_add_mismatched_reso_doesnt_downcast(self): # https://github.com/pandas-dev/pandas/pull/48748#issuecomment-1260181008 td = pd.Timedelta(microseconds=1) dti = pd.date_range("2016-01-01", periods=3) - td - dta = dti._data._as_unit("us") + dta = dti._data.as_unit("us") - res = dta + td._as_unit("us") + res = dta + td.as_unit("us") # even though the result is an even number of days # (so we _could_ downcast to unit="s"), we do not. - assert res._unit == "us" + assert res.unit == "us" @pytest.mark.parametrize( "scalar", @@ -240,32 +240,32 @@ def test_add_timedeltalike_scalar_mismatched_reso(self, dta_dti, scalar): exp_reso = max(dta._creso, td._creso) exp_unit = npy_unit_to_abbrev(exp_reso) - expected = (dti + td)._data._as_unit(exp_unit) + expected = (dti + td)._data.as_unit(exp_unit) result = dta + scalar tm.assert_extension_array_equal(result, expected) result = scalar + dta tm.assert_extension_array_equal(result, expected) - expected = (dti - td)._data._as_unit(exp_unit) + expected = (dti - td)._data.as_unit(exp_unit) result = dta - scalar tm.assert_extension_array_equal(result, expected) def test_sub_datetimelike_scalar_mismatch(self): dti = pd.date_range("2016-01-01", periods=3) - dta = dti._data._as_unit("us") + dta = dti._data.as_unit("us") - ts = dta[0]._as_unit("s") + ts = dta[0].as_unit("s") result = dta - ts - expected = (dti - dti[0])._data._as_unit("us") + expected = (dti - dti[0])._data.as_unit("us") assert result.dtype == "m8[us]" tm.assert_extension_array_equal(result, expected) def test_sub_datetime64_reso_mismatch(self): dti = pd.date_range("2016-01-01", periods=3) - left = dti._data._as_unit("s") - right = left._as_unit("ms") + left = dti._data.as_unit("s") + right = left.as_unit("ms") result = left - right exp_values = np.array([0, 0, 0], dtype="m8[ms]") diff --git a/pandas/tests/arrays/test_timedeltas.py b/pandas/tests/arrays/test_timedeltas.py index f5d50465fee10..2fd7ccc9cf338 100644 --- a/pandas/tests/arrays/test_timedeltas.py +++ b/pandas/tests/arrays/test_timedeltas.py @@ -104,7 +104,7 @@ def test_add_pdnat(self, tda): def test_add_datetimelike_scalar(self, tda, tz_naive_fixture): ts = pd.Timestamp("2016-01-01", tz=tz_naive_fixture) - expected = tda._as_unit("ns") + ts + expected = tda.as_unit("ns") + ts res = tda + ts tm.assert_extension_array_equal(res, expected) res = ts + tda diff --git a/pandas/tests/indexes/datetimes/test_indexing.py b/pandas/tests/indexes/datetimes/test_indexing.py index 1e7cc86616afc..887766dd3fc29 100644 --- a/pandas/tests/indexes/datetimes/test_indexing.py +++ b/pandas/tests/indexes/datetimes/test_indexing.py @@ -388,7 +388,7 @@ def test_take_fill_value_with_timezone(self): class TestGetLoc: def test_get_loc_key_unit_mismatch(self): idx = date_range("2000-01-01", periods=3) - key = idx[1]._as_unit("ms") + key = idx[1].as_unit("ms") loc = idx.get_loc(key) assert loc == 1 assert key in idx @@ -396,7 +396,7 @@ def test_get_loc_key_unit_mismatch(self): def test_get_loc_key_unit_mismatch_not_castable(self): dta = date_range("2000-01-01", periods=3)._data.astype("M8[s]") dti = DatetimeIndex(dta) - key = dta[0]._as_unit("ns") + pd.Timedelta(1) + key = dta[0].as_unit("ns") + pd.Timedelta(1) with pytest.raises( KeyError, match=r"Timestamp\('2000-01-01 00:00:00.000000001'\)" diff --git a/pandas/tests/indexes/timedeltas/test_indexing.py b/pandas/tests/indexes/timedeltas/test_indexing.py index 4b7140b112bd9..12aece23738ec 100644 --- a/pandas/tests/indexes/timedeltas/test_indexing.py +++ b/pandas/tests/indexes/timedeltas/test_indexing.py @@ -77,14 +77,14 @@ def test_timestamp_invalid_key(self, key): class TestGetLoc: def test_get_loc_key_unit_mismatch(self): idx = to_timedelta(["0 days", "1 days", "2 days"]) - key = idx[1]._as_unit("ms") + key = idx[1].as_unit("ms") loc = idx.get_loc(key) assert loc == 1 def test_get_loc_key_unit_mismatch_not_castable(self): tdi = to_timedelta(["0 days", "1 days", "2 days"]).astype("m8[s]") assert tdi.dtype == "m8[s]" - key = tdi[0]._as_unit("ns") + Timedelta(1) + key = tdi[0].as_unit("ns") + Timedelta(1) with pytest.raises(KeyError, match=r"Timedelta\('0 days 00:00:00.000000001'\)"): tdi.get_loc(key) diff --git a/pandas/tests/io/json/test_pandas.py b/pandas/tests/io/json/test_pandas.py index 749075b8637cf..2f3fc4d0fcba8 100644 --- a/pandas/tests/io/json/test_pandas.py +++ b/pandas/tests/io/json/test_pandas.py @@ -970,7 +970,7 @@ def test_mixed_timedelta_datetime(self): ts = Timestamp("20130101") frame = DataFrame({"a": [td, ts]}, dtype=object) - expected = DataFrame({"a": [pd.Timedelta(td)._as_unit("ns").value, ts.value]}) + expected = DataFrame({"a": [pd.Timedelta(td).as_unit("ns").value, ts.value]}) result = read_json(frame.to_json(date_unit="ns"), dtype={"a": "int64"}) tm.assert_frame_equal(result, expected, check_index_type=False) diff --git a/pandas/tests/scalar/test_nat.py b/pandas/tests/scalar/test_nat.py index 7ecca562d4996..e310506935729 100644 --- a/pandas/tests/scalar/test_nat.py +++ b/pandas/tests/scalar/test_nat.py @@ -184,7 +184,7 @@ def test_nat_iso_format(get_nat): @pytest.mark.parametrize( "klass,expected", [ - (Timestamp, ["normalize", "to_julian_date", "to_period"]), + (Timestamp, ["normalize", "to_julian_date", "to_period", "unit"]), ( Timedelta, [ @@ -192,6 +192,7 @@ def test_nat_iso_format(get_nat): "resolution_string", "to_pytimedelta", "to_timedelta64", + "unit", "view", ], ), @@ -254,6 +255,7 @@ def _get_overlap_public_nat_methods(klass, as_tuple=False): ( Timestamp, [ + "as_unit", "astimezone", "ceil", "combine", diff --git a/pandas/tests/scalar/timedelta/test_constructors.py b/pandas/tests/scalar/timedelta/test_constructors.py index 7540813fd302b..dd671e3c9e094 100644 --- a/pandas/tests/scalar/timedelta/test_constructors.py +++ b/pandas/tests/scalar/timedelta/test_constructors.py @@ -313,7 +313,7 @@ def test_construction_out_of_bounds_td64ns(val, unit): assert td.asm8.dtype == "m8[s]" msg = r"Cannot cast 1067\d\d days .* to unit='ns' without overflow" with pytest.raises(OutOfBoundsTimedelta, match=msg): - td._as_unit("ns") + td.as_unit("ns") # But just back in bounds and we are OK assert Timedelta(td64 - 1) == td64 - 1 @@ -324,7 +324,7 @@ def test_construction_out_of_bounds_td64ns(val, unit): td2 = Timedelta(td64) msg = r"Cannot cast -1067\d\d days .* to unit='ns' without overflow" with pytest.raises(OutOfBoundsTimedelta, match=msg): - td2._as_unit("ns") + td2.as_unit("ns") # But just back in bounds and we are OK assert Timedelta(td64 + 1) == td64 + 1 diff --git a/pandas/tests/scalar/timedelta/test_timedelta.py b/pandas/tests/scalar/timedelta/test_timedelta.py index 68b4eea28e367..924f756edb233 100644 --- a/pandas/tests/scalar/timedelta/test_timedelta.py +++ b/pandas/tests/scalar/timedelta/test_timedelta.py @@ -29,29 +29,29 @@ class TestAsUnit: def test_as_unit(self): td = Timedelta(days=1) - assert td._as_unit("ns") is td + assert td.as_unit("ns") is td - res = td._as_unit("us") + res = td.as_unit("us") assert res.value == td.value // 1000 assert res._creso == NpyDatetimeUnit.NPY_FR_us.value - rt = res._as_unit("ns") + rt = res.as_unit("ns") assert rt.value == td.value assert rt._creso == td._creso - res = td._as_unit("ms") + res = td.as_unit("ms") assert res.value == td.value // 1_000_000 assert res._creso == NpyDatetimeUnit.NPY_FR_ms.value - rt = res._as_unit("ns") + rt = res.as_unit("ns") assert rt.value == td.value assert rt._creso == td._creso - res = td._as_unit("s") + res = td.as_unit("s") assert res.value == td.value // 1_000_000_000 assert res._creso == NpyDatetimeUnit.NPY_FR_s.value - rt = res._as_unit("ns") + rt = res.as_unit("ns") assert rt.value == td.value assert rt._creso == td._creso @@ -62,15 +62,15 @@ def test_as_unit_overflows(self): msg = "Cannot cast 106752 days 00:00:00 to unit='ns' without overflow" with pytest.raises(OutOfBoundsTimedelta, match=msg): - td._as_unit("ns") + td.as_unit("ns") - res = td._as_unit("ms") + res = td.as_unit("ms") assert res.value == us // 1000 assert res._creso == NpyDatetimeUnit.NPY_FR_ms.value def test_as_unit_rounding(self): td = Timedelta(microseconds=1500) - res = td._as_unit("ms") + res = td.as_unit("ms") expected = Timedelta(milliseconds=1) assert res == expected @@ -79,18 +79,18 @@ def test_as_unit_rounding(self): assert res.value == 1 with pytest.raises(ValueError, match="Cannot losslessly convert units"): - td._as_unit("ms", round_ok=False) + td.as_unit("ms", round_ok=False) def test_as_unit_non_nano(self): # case where we are going neither to nor from nano - td = Timedelta(days=1)._as_unit("ms") + td = Timedelta(days=1).as_unit("ms") assert td.days == 1 assert td.value == 86_400_000 assert td.components.days == 1 assert td._d == 1 assert td.total_seconds() == 86400 - res = td._as_unit("us") + res = td.as_unit("us") assert res.value == 86_400_000_000 assert res.components.days == 1 assert res.components.hours == 0 @@ -260,7 +260,7 @@ def test_floordiv_numeric(self, td): def test_addsub_mismatched_reso(self, td): # need to cast to since td is out of bounds for ns, so # so we would raise OverflowError without casting - other = Timedelta(days=1)._as_unit("us") + other = Timedelta(days=1).as_unit("us") # td is out of bounds for ns result = td + other @@ -754,7 +754,7 @@ def test_round_sanity(self, val, method): @pytest.mark.parametrize("unit", ["ns", "us", "ms", "s"]) def test_round_non_nano(self, unit): - td = Timedelta("1 days 02:34:57")._as_unit(unit) + td = Timedelta("1 days 02:34:57").as_unit(unit) res = td.round("min") assert res == Timedelta("1 days 02:35:00") diff --git a/pandas/tests/scalar/timestamp/test_timestamp.py b/pandas/tests/scalar/timestamp/test_timestamp.py index 2d9deff13322b..f5b9a35a53a24 100644 --- a/pandas/tests/scalar/timestamp/test_timestamp.py +++ b/pandas/tests/scalar/timestamp/test_timestamp.py @@ -756,7 +756,7 @@ def test_cmp_cross_reso(self): # subtracting 3600*24 gives a datetime64 that _can_ fit inside the # nanosecond implementation bounds. - other = Timestamp(dt64 - 3600 * 24)._as_unit("ns") + other = Timestamp(dt64 - 3600 * 24).as_unit("ns") assert other < ts assert other.asm8 > ts.asm8 # <- numpy gets this wrong assert ts > other @@ -870,7 +870,7 @@ def test_sub_datetimelike_mismatched_reso(self, ts_tz): NpyDatetimeUnit.NPY_FR_ms.value: "s", NpyDatetimeUnit.NPY_FR_s.value: "us", }[ts._creso] - other = ts._as_unit(unit) + other = ts.as_unit(unit) assert other._creso != ts._creso result = ts - other @@ -886,7 +886,7 @@ def test_sub_datetimelike_mismatched_reso(self, ts_tz): if ts._creso < other._creso: # Case where rounding is lossy other2 = other + Timedelta._from_value_and_reso(1, other._creso) - exp = ts._as_unit(other._unit) - other2 + exp = ts.as_unit(other.unit) - other2 res = ts - other2 assert res == exp @@ -897,7 +897,7 @@ def test_sub_datetimelike_mismatched_reso(self, ts_tz): assert res._creso == max(ts._creso, other._creso) else: ts2 = ts + Timedelta._from_value_and_reso(1, ts._creso) - exp = ts2 - other._as_unit(ts2._unit) + exp = ts2 - other.as_unit(ts2.unit) res = ts2 - other assert res == exp @@ -918,7 +918,7 @@ def test_sub_timedeltalike_mismatched_reso(self, ts_tz): NpyDatetimeUnit.NPY_FR_ms.value: "s", NpyDatetimeUnit.NPY_FR_s.value: "us", }[ts._creso] - other = Timedelta(0)._as_unit(unit) + other = Timedelta(0).as_unit(unit) assert other._creso != ts._creso result = ts + other @@ -934,7 +934,7 @@ def test_sub_timedeltalike_mismatched_reso(self, ts_tz): if ts._creso < other._creso: # Case where rounding is lossy other2 = other + Timedelta._from_value_and_reso(1, other._creso) - exp = ts._as_unit(other._unit) + other2 + exp = ts.as_unit(other.unit) + other2 res = ts + other2 assert res == exp assert res._creso == max(ts._creso, other._creso) @@ -943,7 +943,7 @@ def test_sub_timedeltalike_mismatched_reso(self, ts_tz): assert res._creso == max(ts._creso, other._creso) else: ts2 = ts + Timedelta._from_value_and_reso(1, ts._creso) - exp = ts2 + other._as_unit(ts2._unit) + exp = ts2 + other.as_unit(ts2.unit) res = ts2 + other assert res == exp @@ -954,8 +954,8 @@ def test_sub_timedeltalike_mismatched_reso(self, ts_tz): def test_addition_doesnt_downcast_reso(self): # https://github.com/pandas-dev/pandas/pull/48748#pullrequestreview-1122635413 - ts = Timestamp(year=2022, month=1, day=1, microsecond=999999)._as_unit("us") - td = Timedelta(microseconds=1)._as_unit("us") + ts = Timestamp(year=2022, month=1, day=1, microsecond=999999).as_unit("us") + td = Timedelta(microseconds=1).as_unit("us") res = ts + td assert res._creso == ts._creso @@ -963,7 +963,7 @@ def test_sub_timedelta64_mismatched_reso(self, ts_tz): ts = ts_tz res = ts + np.timedelta64(1, "ns") - exp = ts._as_unit("ns") + np.timedelta64(1, "ns") + exp = ts.as_unit("ns") + np.timedelta64(1, "ns") assert exp == res assert exp._creso == NpyDatetimeUnit.NPY_FR_ns.value @@ -1001,29 +1001,29 @@ class TestAsUnit: def test_as_unit(self): ts = Timestamp("1970-01-01") - assert ts._as_unit("ns") is ts + assert ts.as_unit("ns") is ts - res = ts._as_unit("us") + res = ts.as_unit("us") assert res.value == ts.value // 1000 assert res._creso == NpyDatetimeUnit.NPY_FR_us.value - rt = res._as_unit("ns") + rt = res.as_unit("ns") assert rt.value == ts.value assert rt._creso == ts._creso - res = ts._as_unit("ms") + res = ts.as_unit("ms") assert res.value == ts.value // 1_000_000 assert res._creso == NpyDatetimeUnit.NPY_FR_ms.value - rt = res._as_unit("ns") + rt = res.as_unit("ns") assert rt.value == ts.value assert rt._creso == ts._creso - res = ts._as_unit("s") + res = ts.as_unit("s") assert res.value == ts.value // 1_000_000_000 assert res._creso == NpyDatetimeUnit.NPY_FR_s.value - rt = res._as_unit("ns") + rt = res.as_unit("ns") assert rt.value == ts.value assert rt._creso == ts._creso @@ -1034,15 +1034,15 @@ def test_as_unit_overflows(self): msg = "Cannot cast 2262-04-12 00:00:00 to unit='ns' without overflow" with pytest.raises(OutOfBoundsDatetime, match=msg): - ts._as_unit("ns") + ts.as_unit("ns") - res = ts._as_unit("ms") + res = ts.as_unit("ms") assert res.value == us // 1000 assert res._creso == NpyDatetimeUnit.NPY_FR_ms.value def test_as_unit_rounding(self): ts = Timestamp(1_500_000) # i.e. 1500 microseconds - res = ts._as_unit("ms") + res = ts.as_unit("ms") expected = Timestamp(1_000_000) # i.e. 1 millisecond assert res == expected @@ -1051,17 +1051,17 @@ def test_as_unit_rounding(self): assert res.value == 1 with pytest.raises(ValueError, match="Cannot losslessly convert units"): - ts._as_unit("ms", round_ok=False) + ts.as_unit("ms", round_ok=False) def test_as_unit_non_nano(self): # case where we are going neither to nor from nano - ts = Timestamp("1970-01-02")._as_unit("ms") + ts = Timestamp("1970-01-02").as_unit("ms") assert ts.year == 1970 assert ts.month == 1 assert ts.day == 2 assert ts.hour == ts.minute == ts.second == ts.microsecond == ts.nanosecond == 0 - res = ts._as_unit("s") + res = ts.as_unit("s") assert res.value == 24 * 3600 assert res.year == 1970 assert res.month == 1 diff --git a/pandas/tests/scalar/timestamp/test_timezones.py b/pandas/tests/scalar/timestamp/test_timezones.py index a05da73ac3031..912b7d9232abe 100644 --- a/pandas/tests/scalar/timestamp/test_timezones.py +++ b/pandas/tests/scalar/timestamp/test_timezones.py @@ -62,7 +62,7 @@ def test_tz_localize_pushes_out_of_bounds(self): def test_tz_localize_ambiguous_bool(self, unit): # make sure that we are correctly accepting bool values as ambiguous # GH#14402 - ts = Timestamp("2015-11-01 01:00:03")._as_unit(unit) + ts = Timestamp("2015-11-01 01:00:03").as_unit(unit) expected0 = Timestamp("2015-11-01 01:00:03-0500", tz="US/Central") expected1 = Timestamp("2015-11-01 01:00:03-0600", tz="US/Central") @@ -257,7 +257,7 @@ def test_timestamp_tz_localize_nonexistent_shift( tz = tz_type + tz if isinstance(shift, str): shift = "shift_" + shift - ts = Timestamp(start_ts)._as_unit(unit) + ts = Timestamp(start_ts).as_unit(unit) result = ts.tz_localize(tz, nonexistent=shift) expected = Timestamp(end_ts).tz_localize(tz) @@ -286,7 +286,7 @@ def test_timestamp_tz_localize_nonexistent_shift_invalid(self, offset, tz_type): @pytest.mark.parametrize("unit", ["ns", "us", "ms", "s"]) def test_timestamp_tz_localize_nonexistent_NaT(self, tz, unit): # GH 8917 - ts = Timestamp("2015-03-29 02:20:00")._as_unit(unit) + ts = Timestamp("2015-03-29 02:20:00").as_unit(unit) result = ts.tz_localize(tz, nonexistent="NaT") assert result is NaT @@ -294,7 +294,7 @@ def test_timestamp_tz_localize_nonexistent_NaT(self, tz, unit): @pytest.mark.parametrize("unit", ["ns", "us", "ms", "s"]) def test_timestamp_tz_localize_nonexistent_raise(self, tz, unit): # GH 8917 - ts = Timestamp("2015-03-29 02:20:00")._as_unit(unit) + ts = Timestamp("2015-03-29 02:20:00").as_unit(unit) msg = "2015-03-29 02:20:00" with pytest.raises(pytz.NonExistentTimeError, match=msg): ts.tz_localize(tz, nonexistent="raise") diff --git a/pandas/tests/scalar/timestamp/test_unary_ops.py b/pandas/tests/scalar/timestamp/test_unary_ops.py index 6d9cfa51d2210..1c1f3acc8331f 100644 --- a/pandas/tests/scalar/timestamp/test_unary_ops.py +++ b/pandas/tests/scalar/timestamp/test_unary_ops.py @@ -150,7 +150,7 @@ def test_round_minute_freq(self, test_input, freq, expected, rounder): @pytest.mark.parametrize("unit", ["ns", "us", "ms", "s"]) def test_ceil(self, unit): - dt = Timestamp("20130101 09:10:11")._as_unit(unit) + dt = Timestamp("20130101 09:10:11").as_unit(unit) result = dt.ceil("D") expected = Timestamp("20130102") assert result == expected @@ -158,7 +158,7 @@ def test_ceil(self, unit): @pytest.mark.parametrize("unit", ["ns", "us", "ms", "s"]) def test_floor(self, unit): - dt = Timestamp("20130101 09:10:11")._as_unit(unit) + dt = Timestamp("20130101 09:10:11").as_unit(unit) result = dt.floor("D") expected = Timestamp("20130101") assert result == expected @@ -172,7 +172,7 @@ def test_floor(self, unit): def test_round_dst_border_ambiguous(self, method, unit): # GH 18946 round near "fall back" DST ts = Timestamp("2017-10-29 00:00:00", tz="UTC").tz_convert("Europe/Madrid") - ts = ts._as_unit(unit) + ts = ts.as_unit(unit) # result = getattr(ts, method)("H", ambiguous=True) assert result == ts @@ -206,7 +206,7 @@ def test_round_dst_border_ambiguous(self, method, unit): ) def test_round_dst_border_nonexistent(self, method, ts_str, freq, unit): # GH 23324 round near "spring forward" DST - ts = Timestamp(ts_str, tz="America/Chicago")._as_unit(unit) + ts = Timestamp(ts_str, tz="America/Chicago").as_unit(unit) result = getattr(ts, method)(freq, nonexistent="shift_forward") expected = Timestamp("2018-03-11 03:00:00", tz="America/Chicago") assert result == expected @@ -486,7 +486,7 @@ def test_replace_across_dst(self, tz, normalize): @pytest.mark.parametrize("unit", ["ns", "us", "ms", "s"]) def test_replace_dst_border(self, unit): # Gh 7825 - t = Timestamp("2013-11-3", tz="America/Chicago")._as_unit(unit) + t = Timestamp("2013-11-3", tz="America/Chicago").as_unit(unit) result = t.replace(hour=3) expected = Timestamp("2013-11-3 03:00:00", tz="America/Chicago") assert result == expected @@ -498,7 +498,7 @@ def test_replace_dst_border(self, unit): def test_replace_dst_fold(self, fold, tz, unit): # GH 25017 d = datetime(2019, 10, 27, 2, 30) - ts = Timestamp(d, tz=tz)._as_unit(unit) + ts = Timestamp(d, tz=tz).as_unit(unit) result = ts.replace(hour=1, fold=fold) expected = Timestamp(datetime(2019, 10, 27, 1, 30)).tz_localize( tz, ambiguous=not fold @@ -513,7 +513,7 @@ def test_replace_dst_fold(self, fold, tz, unit): @pytest.mark.parametrize("unit", ["ns", "us", "ms", "s"]) def test_normalize(self, tz_naive_fixture, arg, unit): tz = tz_naive_fixture - ts = Timestamp(arg, tz=tz)._as_unit(unit) + ts = Timestamp(arg, tz=tz).as_unit(unit) result = ts.normalize() expected = Timestamp("2013-11-30", tz=tz) assert result == expected diff --git a/pandas/tests/series/test_constructors.py b/pandas/tests/series/test_constructors.py index 37348bb743537..d69809f42b5a1 100644 --- a/pandas/tests/series/test_constructors.py +++ b/pandas/tests/series/test_constructors.py @@ -1582,7 +1582,7 @@ def test_convert_non_ns(self): assert ser.dtype == arr.dtype tdi = timedelta_range("00:00:01", periods=3, freq="s") - tda = tdi._data._as_unit("s") + tda = tdi._data.as_unit("s") expected = Series(tda) assert expected.dtype == arr.dtype tm.assert_series_equal(ser, expected) diff --git a/pandas/tests/tseries/offsets/test_offsets.py b/pandas/tests/tseries/offsets/test_offsets.py index 29b82f27234a5..63594c2b2c48a 100644 --- a/pandas/tests/tseries/offsets/test_offsets.py +++ b/pandas/tests/tseries/offsets/test_offsets.py @@ -583,8 +583,8 @@ def test_add_dt64_ndarray_non_nano(self, offset_types, unit, request): exp_unit = unit if isinstance(off, Tick) and off._creso > dta._creso: # cast to higher reso like we would with Timedelta scalar - exp_unit = Timedelta(off)._unit - expected = expected._as_unit(exp_unit) + exp_unit = Timedelta(off).unit + expected = expected.as_unit(exp_unit) if len(w): # PerformanceWarning was issued bc _apply_array raised, so we