Skip to content

Commit f3c46cd

Browse files
authored
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
1 parent 72e923e commit f3c46cd

27 files changed

+156
-103
lines changed

doc/source/reference/arrays.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ Properties
139139
Timestamp.second
140140
Timestamp.tz
141141
Timestamp.tzinfo
142+
Timestamp.unit
142143
Timestamp.value
143144
Timestamp.week
144145
Timestamp.weekofyear
@@ -149,6 +150,7 @@ Methods
149150
.. autosummary::
150151
:toctree: api/
151152

153+
Timestamp.as_unit
152154
Timestamp.astimezone
153155
Timestamp.ceil
154156
Timestamp.combine
@@ -242,6 +244,7 @@ Properties
242244
Timedelta.nanoseconds
243245
Timedelta.resolution
244246
Timedelta.seconds
247+
Timedelta.unit
245248
Timedelta.value
246249
Timedelta.view
247250

@@ -250,6 +253,7 @@ Methods
250253
.. autosummary::
251254
:toctree: api/
252255

256+
Timedelta.as_unit
253257
Timedelta.ceil
254258
Timedelta.floor
255259
Timedelta.isoformat

pandas/_libs/tslib.pyx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ cpdef array_to_datetime(
551551
raise ValueError('Cannot mix tz-aware with '
552552
'tz-naive values')
553553
if isinstance(val, _Timestamp):
554-
iresult[i] = val._as_unit("ns").value
554+
iresult[i] = val.as_unit("ns").value
555555
else:
556556
iresult[i] = pydatetime_to_dt64(val, &dts)
557557
check_dts_bounds(&dts)
@@ -906,7 +906,7 @@ def array_to_datetime_with_tz(ndarray values, tzinfo tz):
906906
else:
907907
# datetime64, tznaive pydatetime, int, float
908908
ts = ts.tz_localize(tz)
909-
ts = ts._as_unit("ns")
909+
ts = ts.as_unit("ns")
910910
ival = ts.value
911911

912912
# Analogous to: result[i] = ival

pandas/_libs/tslibs/nattype.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,4 @@ class NaTType:
127127
__le__: _NatComparison
128128
__gt__: _NatComparison
129129
__ge__: _NatComparison
130+
def as_unit(self, unit: str, round_ok: bool = ...) -> NaTType: ...

pandas/_libs/tslibs/nattype.pyx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,22 @@ default 'raise'
11951195
def tzinfo(self) -> None:
11961196
return None
11971197

1198+
def as_unit(self, str unit, bint round_ok=True) -> "NaTType":
1199+
"""
1200+
Convert the underlying int64 representaton to the given unit.
1201+
1202+
Parameters
1203+
----------
1204+
unit : {"ns", "us", "ms", "s"}
1205+
round_ok : bool, default True
1206+
If False and the conversion requires rounding, raise.
1207+
1208+
Returns
1209+
-------
1210+
Timestamp
1211+
"""
1212+
return c_NaT
1213+
11981214

11991215
c_NaT = NaTType() # C-visible
12001216
NaT = c_NaT # Python-visible

pandas/_libs/tslibs/timedeltas.pyi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,5 +152,5 @@ class Timedelta(timedelta):
152152
def to_numpy(self) -> np.timedelta64: ...
153153
def view(self, dtype: npt.DTypeLike = ...) -> object: ...
154154
@property
155-
def _unit(self) -> str: ...
156-
def _as_unit(self, unit: str, round_ok: bool = ...) -> Timedelta: ...
155+
def unit(self) -> str: ...
156+
def as_unit(self, unit: str, round_ok: bool = ...) -> Timedelta: ...

pandas/_libs/tslibs/timedeltas.pyx

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ cdef convert_to_timedelta64(object ts, str unit):
339339
elif isinstance(ts, _Timedelta):
340340
# already in the proper format
341341
if ts._creso != NPY_FR_ns:
342-
ts = ts._as_unit("ns").asm8
342+
ts = ts.as_unit("ns").asm8
343343
else:
344344
ts = np.timedelta64(ts.value, "ns")
345345
elif is_timedelta64_object(ts):
@@ -1081,6 +1081,10 @@ cdef class _Timedelta(timedelta):
10811081
# TODO: add nanos/1e9?
10821082
return self.days * 24 * 3600 + self.seconds + self.microseconds / 1_000_000
10831083

1084+
@property
1085+
def unit(self) -> str:
1086+
return npy_unit_to_abbrev(self._creso)
1087+
10841088
def __hash__(_Timedelta self):
10851089
if self._has_ns():
10861090
# Note: this does *not* satisfy the invariance
@@ -1500,7 +1504,20 @@ cdef class _Timedelta(timedelta):
15001504
# exposing as classmethod for testing
15011505
return _timedelta_from_value_and_reso(value, reso)
15021506

1503-
def _as_unit(self, str unit, bint round_ok=True):
1507+
def as_unit(self, str unit, bint round_ok=True):
1508+
"""
1509+
Convert the underlying int64 representaton to the given unit.
1510+
1511+
Parameters
1512+
----------
1513+
unit : {"ns", "us", "ms", "s"}
1514+
round_ok : bool, default True
1515+
If False and the conversion requires rounding, raise.
1516+
1517+
Returns
1518+
-------
1519+
Timedelta
1520+
"""
15041521
dtype = np.dtype(f"m8[{unit}]")
15051522
reso = get_unit_from_dtype(dtype)
15061523
return self._as_creso(reso, round_ok=round_ok)

pandas/_libs/tslibs/timestamps.pyi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,5 +220,5 @@ class Timestamp(datetime):
220220
@property
221221
def daysinmonth(self) -> int: ...
222222
@property
223-
def _unit(self) -> str: ...
224-
def _as_unit(self, unit: str, round_ok: bool = ...) -> Timestamp: ...
223+
def unit(self) -> str: ...
224+
def as_unit(self, unit: str, round_ok: bool = ...) -> Timestamp: ...

pandas/_libs/tslibs/timestamps.pyx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ cdef class _Timestamp(ABCTimestamp):
233233
resolution = MinMaxReso("resolution") # GH#21336, GH#21365
234234

235235
@property
236-
def _unit(self) -> str:
236+
def unit(self) -> str:
237237
"""
238238
The abbreviation associated with self._creso.
239239
"""
@@ -993,7 +993,20 @@ cdef class _Timestamp(ABCTimestamp):
993993
value = convert_reso(self.value, self._creso, reso, round_ok=round_ok)
994994
return type(self)._from_value_and_reso(value, reso=reso, tz=self.tzinfo)
995995

996-
def _as_unit(self, str unit, bint round_ok=True):
996+
def as_unit(self, str unit, bint round_ok=True):
997+
"""
998+
Convert the underlying int64 representaton to the given unit.
999+
1000+
Parameters
1001+
----------
1002+
unit : {"ns", "us", "ms", "s"}
1003+
round_ok : bool, default True
1004+
If False and the conversion requires rounding, raise.
1005+
1006+
Returns
1007+
-------
1008+
Timestamp
1009+
"""
9971010
dtype = np.dtype(f"M8[{unit}]")
9981011
reso = get_unit_from_dtype(dtype)
9991012
try:

pandas/core/arrays/datetimelike.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -816,7 +816,7 @@ def isin(self, values) -> npt.NDArray[np.bool_]:
816816

817817
if self.dtype.kind in ["m", "M"]:
818818
self = cast("DatetimeArray | TimedeltaArray", self)
819-
values = values._as_unit(self._unit)
819+
values = values.as_unit(self.unit)
820820

821821
try:
822822
self._check_compatible_with(values)
@@ -1116,7 +1116,7 @@ def _add_datetimelike_scalar(self, other) -> DatetimeArray:
11161116
# i.e. np.datetime64("NaT")
11171117
# In this case we specifically interpret NaT as a datetime, not
11181118
# the timedelta interpretation we would get by returning self + NaT
1119-
result = self._ndarray + NaT.to_datetime64().astype(f"M8[{self._unit}]")
1119+
result = self._ndarray + NaT.to_datetime64().astype(f"M8[{self.unit}]")
11201120
# Preserve our resolution
11211121
return DatetimeArray._simple_new(result, dtype=result.dtype)
11221122

@@ -1128,10 +1128,10 @@ def _add_datetimelike_scalar(self, other) -> DatetimeArray:
11281128
result = checked_add_with_arr(
11291129
self.asi8, other_i8, arr_mask=self._isnan, b_mask=o_mask
11301130
)
1131-
res_values = result.view(f"M8[{self._unit}]")
1131+
res_values = result.view(f"M8[{self.unit}]")
11321132

1133-
dtype = tz_to_dtype(tz=other.tz, unit=self._unit)
1134-
res_values = result.view(f"M8[{self._unit}]")
1133+
dtype = tz_to_dtype(tz=other.tz, unit=self.unit)
1134+
res_values = result.view(f"M8[{self.unit}]")
11351135
new_freq = self._get_arithmetic_result_freq(other)
11361136
return DatetimeArray._simple_new(res_values, dtype=dtype, freq=new_freq)
11371137

@@ -1191,7 +1191,7 @@ def _sub_datetimelike(self, other: Timestamp | DatetimeArray) -> TimedeltaArray:
11911191
res_values = checked_add_with_arr(
11921192
self.asi8, -other_i8, arr_mask=self._isnan, b_mask=o_mask
11931193
)
1194-
res_m8 = res_values.view(f"timedelta64[{self._unit}]")
1194+
res_m8 = res_values.view(f"timedelta64[{self.unit}]")
11951195

11961196
new_freq = self._get_arithmetic_result_freq(other)
11971197
return TimedeltaArray._simple_new(res_m8, dtype=res_m8.dtype, freq=new_freq)
@@ -1989,13 +1989,13 @@ def _creso(self) -> int:
19891989
return get_unit_from_dtype(self._ndarray.dtype)
19901990

19911991
@cache_readonly
1992-
def _unit(self) -> str:
1992+
def unit(self) -> str:
19931993
# e.g. "ns", "us", "ms"
19941994
# error: Argument 1 to "dtype_to_unit" has incompatible type
19951995
# "ExtensionDtype"; expected "Union[DatetimeTZDtype, dtype[Any]]"
19961996
return dtype_to_unit(self.dtype) # type: ignore[arg-type]
19971997

1998-
def _as_unit(self: TimelikeOpsT, unit: str) -> TimelikeOpsT:
1998+
def as_unit(self: TimelikeOpsT, unit: str) -> TimelikeOpsT:
19991999
dtype = np.dtype(f"{self.dtype.kind}8[{unit}]")
20002000
new_values = astype_overflowsafe(self._ndarray, dtype, round_ok=True)
20012001

@@ -2017,9 +2017,9 @@ def _ensure_matching_resos(self, other):
20172017
if self._creso != other._creso:
20182018
# Just as with Timestamp/Timedelta, we cast to the higher resolution
20192019
if self._creso < other._creso:
2020-
self = self._as_unit(other._unit)
2020+
self = self.as_unit(other.unit)
20212021
else:
2022-
other = other._as_unit(self._unit)
2022+
other = other.as_unit(self.unit)
20232023
return self, other
20242024

20252025
# --------------------------------------------------------------

pandas/core/arrays/datetimes.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -351,9 +351,9 @@ def _from_sequence_not_strict(
351351
data_unit = np.datetime_data(subarr.dtype)[0]
352352
data_dtype = tz_to_dtype(tz, data_unit)
353353
result = cls._simple_new(subarr, freq=freq, dtype=data_dtype)
354-
if unit is not None and unit != result._unit:
354+
if unit is not None and unit != result.unit:
355355
# If unit was specified in user-passed dtype, cast to it here
356-
result = result._as_unit(unit)
356+
result = result.as_unit(unit)
357357

358358
if inferred_freq is None and freq is not None:
359359
# this condition precludes `freq_infer`
@@ -843,7 +843,7 @@ def tz_convert(self, tz) -> DatetimeArray:
843843
)
844844

845845
# No conversion since timestamps are all UTC to begin with
846-
dtype = tz_to_dtype(tz, unit=self._unit)
846+
dtype = tz_to_dtype(tz, unit=self.unit)
847847
return self._simple_new(self._ndarray, dtype=dtype, freq=self.freq)
848848

849849
@dtl.ravel_compat
@@ -1018,8 +1018,8 @@ def tz_localize(
10181018
nonexistent=nonexistent,
10191019
creso=self._creso,
10201020
)
1021-
new_dates = new_dates.view(f"M8[{self._unit}]")
1022-
dtype = tz_to_dtype(tz, unit=self._unit)
1021+
new_dates = new_dates.view(f"M8[{self.unit}]")
1022+
dtype = tz_to_dtype(tz, unit=self.unit)
10231023

10241024
freq = None
10251025
if timezones.is_utc(tz) or (len(self) == 1 and not isna(new_dates[0])):

0 commit comments

Comments
 (0)