Skip to content

Commit 8d259c0

Browse files
authored
REF: get_supported_reso->get_supported_dtype (pandas-dev#56439)
1 parent 5b6723c commit 8d259c0

16 files changed

+79
-90
lines changed

pandas/_libs/tslibs/__init__.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,16 @@
3030
"get_unit_from_dtype",
3131
"periods_per_day",
3232
"periods_per_second",
33-
"is_supported_unit",
34-
"npy_unit_to_abbrev",
35-
"get_supported_reso",
3633
"guess_datetime_format",
3734
"add_overflowsafe",
35+
"get_supported_dtype",
36+
"is_supported_dtype",
3837
]
3938

4039
from pandas._libs.tslibs import dtypes # pylint: disable=import-self
4140
from pandas._libs.tslibs.conversion import localize_pydatetime
4241
from pandas._libs.tslibs.dtypes import (
4342
Resolution,
44-
get_supported_reso,
45-
is_supported_unit,
46-
npy_unit_to_abbrev,
4743
periods_per_day,
4844
periods_per_second,
4945
)
@@ -58,6 +54,8 @@
5854
OutOfBoundsTimedelta,
5955
add_overflowsafe,
6056
astype_overflowsafe,
57+
get_supported_dtype,
58+
is_supported_dtype,
6159
is_unitless,
6260
py_get_unit_from_dtype as get_unit_from_dtype,
6361
)

pandas/_libs/tslibs/dtypes.pxd

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ from numpy cimport int64_t
33
from pandas._libs.tslibs.np_datetime cimport NPY_DATETIMEUNIT
44

55

6-
cpdef str npy_unit_to_abbrev(NPY_DATETIMEUNIT unit)
6+
cdef str npy_unit_to_abbrev(NPY_DATETIMEUNIT unit)
77
cpdef NPY_DATETIMEUNIT abbrev_to_npy_unit(str abbrev)
88
cdef NPY_DATETIMEUNIT freq_group_code_to_npy_unit(int freq) noexcept nogil
99
cpdef int64_t periods_per_day(NPY_DATETIMEUNIT reso=*) except? -1
1010
cpdef int64_t periods_per_second(NPY_DATETIMEUNIT reso) except? -1
11-
cpdef NPY_DATETIMEUNIT get_supported_reso(NPY_DATETIMEUNIT reso)
12-
cpdef bint is_supported_unit(NPY_DATETIMEUNIT reso)
11+
cdef NPY_DATETIMEUNIT get_supported_reso(NPY_DATETIMEUNIT reso)
12+
cdef bint is_supported_unit(NPY_DATETIMEUNIT reso)
1313

1414
cpdef freq_to_period_freqstr(freq_n, freq_name)
1515
cdef dict c_OFFSET_TO_PERIOD_FREQSTR

pandas/_libs/tslibs/dtypes.pyi

-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@ OFFSET_TO_PERIOD_FREQSTR: dict[str, str]
44

55
def periods_per_day(reso: int = ...) -> int: ...
66
def periods_per_second(reso: int) -> int: ...
7-
def is_supported_unit(reso: int) -> bool: ...
8-
def npy_unit_to_abbrev(unit: int) -> str: ...
9-
def get_supported_reso(reso: int) -> int: ...
107
def abbrev_to_npy_unit(abbrev: str) -> int: ...
118
def freq_to_period_freqstr(freq_n: int, freq_name: str) -> str: ...
129

pandas/_libs/tslibs/dtypes.pyx

+3-3
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,7 @@ class NpyDatetimeUnit(Enum):
559559
NPY_FR_GENERIC = NPY_DATETIMEUNIT.NPY_FR_GENERIC
560560

561561

562-
cpdef NPY_DATETIMEUNIT get_supported_reso(NPY_DATETIMEUNIT reso):
562+
cdef NPY_DATETIMEUNIT get_supported_reso(NPY_DATETIMEUNIT reso):
563563
# If we have an unsupported reso, return the nearest supported reso.
564564
if reso == NPY_DATETIMEUNIT.NPY_FR_GENERIC:
565565
# TODO: or raise ValueError? trying this gives unraisable errors, but
@@ -572,7 +572,7 @@ cpdef NPY_DATETIMEUNIT get_supported_reso(NPY_DATETIMEUNIT reso):
572572
return reso
573573

574574

575-
cpdef bint is_supported_unit(NPY_DATETIMEUNIT reso):
575+
cdef bint is_supported_unit(NPY_DATETIMEUNIT reso):
576576
return (
577577
reso == NPY_DATETIMEUNIT.NPY_FR_ns
578578
or reso == NPY_DATETIMEUNIT.NPY_FR_us
@@ -581,7 +581,7 @@ cpdef bint is_supported_unit(NPY_DATETIMEUNIT reso):
581581
)
582582

583583

584-
cpdef str npy_unit_to_abbrev(NPY_DATETIMEUNIT unit):
584+
cdef str npy_unit_to_abbrev(NPY_DATETIMEUNIT unit):
585585
if unit == NPY_DATETIMEUNIT.NPY_FR_ns or unit == NPY_DATETIMEUNIT.NPY_FR_GENERIC:
586586
# generic -> default to nanoseconds
587587
return "ns"

pandas/_libs/tslibs/np_datetime.pyi

+2
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,5 @@ def add_overflowsafe(
2323
left: npt.NDArray[np.int64],
2424
right: npt.NDArray[np.int64],
2525
) -> npt.NDArray[np.int64]: ...
26+
def get_supported_dtype(dtype: np.dtype) -> np.dtype: ...
27+
def is_supported_dtype(dtype: np.dtype) -> bool: ...

pandas/_libs/tslibs/np_datetime.pyx

+24
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ from numpy cimport (
3939
)
4040

4141
from pandas._libs.tslibs.dtypes cimport (
42+
get_supported_reso,
43+
is_supported_unit,
4244
npy_unit_to_abbrev,
4345
npy_unit_to_attrname,
4446
)
@@ -91,6 +93,28 @@ def py_get_unit_from_dtype(dtype):
9193
return get_unit_from_dtype(dtype)
9294

9395

96+
def get_supported_dtype(dtype: cnp.dtype) -> cnp.dtype:
97+
reso = get_unit_from_dtype(dtype)
98+
new_reso = get_supported_reso(reso)
99+
new_unit = npy_unit_to_abbrev(new_reso)
100+
101+
# Accessing dtype.kind here incorrectly(?) gives "" instead of "m"/"M",
102+
# so we check type_num instead
103+
if dtype.type_num == cnp.NPY_DATETIME:
104+
new_dtype = np.dtype(f"M8[{new_unit}]")
105+
else:
106+
new_dtype = np.dtype(f"m8[{new_unit}]")
107+
return new_dtype
108+
109+
110+
def is_supported_dtype(dtype: cnp.dtype) -> bool:
111+
if dtype.type_num not in [cnp.NPY_DATETIME, cnp.NPY_TIMEDELTA]:
112+
raise ValueError("is_unitless dtype must be datetime64 or timedelta64")
113+
cdef:
114+
NPY_DATETIMEUNIT unit = get_unit_from_dtype(dtype)
115+
return is_supported_unit(unit)
116+
117+
94118
def is_unitless(dtype: cnp.dtype) -> bool:
95119
"""
96120
Check if a datetime64 or timedelta64 dtype has no attached unit.

pandas/core/arrays/_mixins.py

+3-10
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@
1313

1414
from pandas._libs import lib
1515
from pandas._libs.arrays import NDArrayBacked
16-
from pandas._libs.tslibs import (
17-
get_unit_from_dtype,
18-
is_supported_unit,
19-
)
16+
from pandas._libs.tslibs import is_supported_dtype
2017
from pandas._typing import (
2118
ArrayLike,
2219
AxisInt,
@@ -141,16 +138,12 @@ def view(self, dtype: Dtype | None = None) -> ArrayLike:
141138
cls = dtype.construct_array_type() # type: ignore[assignment]
142139
dt64_values = arr.view(f"M8[{dtype.unit}]")
143140
return cls(dt64_values, dtype=dtype)
144-
elif lib.is_np_dtype(dtype, "M") and is_supported_unit(
145-
get_unit_from_dtype(dtype)
146-
):
141+
elif lib.is_np_dtype(dtype, "M") and is_supported_dtype(dtype):
147142
from pandas.core.arrays import DatetimeArray
148143

149144
dt64_values = arr.view(dtype)
150145
return DatetimeArray(dt64_values, dtype=dtype)
151-
elif lib.is_np_dtype(dtype, "m") and is_supported_unit(
152-
get_unit_from_dtype(dtype)
153-
):
146+
elif lib.is_np_dtype(dtype, "m") and is_supported_dtype(dtype):
154147
from pandas.core.arrays import TimedeltaArray
155148

156149
td64_values = arr.view(dtype)

pandas/core/arrays/datetimes.py

+8-12
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,13 @@
2727
astype_overflowsafe,
2828
fields,
2929
get_resolution,
30-
get_supported_reso,
30+
get_supported_dtype,
3131
get_unit_from_dtype,
3232
ints_to_pydatetime,
3333
is_date_array_normalized,
34-
is_supported_unit,
34+
is_supported_dtype,
3535
is_unitless,
3636
normalize_i8_timestamps,
37-
npy_unit_to_abbrev,
3837
timezones,
3938
to_offset,
4039
tz_convert_from_utc,
@@ -712,7 +711,7 @@ def astype(self, dtype, copy: bool = True):
712711
self.tz is None
713712
and lib.is_np_dtype(dtype, "M")
714713
and not is_unitless(dtype)
715-
and is_supported_unit(get_unit_from_dtype(dtype))
714+
and is_supported_dtype(dtype)
716715
):
717716
# unit conversion e.g. datetime64[s]
718717
res_values = astype_overflowsafe(self._ndarray, dtype, copy=True)
@@ -2307,7 +2306,7 @@ def _sequence_to_dt64(
23072306
assert isinstance(result, np.ndarray), type(result)
23082307
assert result.dtype.kind == "M"
23092308
assert result.dtype != "M8"
2310-
assert is_supported_unit(get_unit_from_dtype(result.dtype))
2309+
assert is_supported_dtype(result.dtype)
23112310
return result, tz
23122311

23132312

@@ -2321,14 +2320,10 @@ def _construct_from_dt64_naive(
23212320
# lib.is_np_dtype(data.dtype)
23222321

23232322
new_dtype = data.dtype
2324-
data_unit = get_unit_from_dtype(new_dtype)
2325-
if not is_supported_unit(data_unit):
2323+
if not is_supported_dtype(new_dtype):
23262324
# Cast to the nearest supported unit, generally "s"
2327-
new_reso = get_supported_reso(data_unit)
2328-
new_unit = npy_unit_to_abbrev(new_reso)
2329-
new_dtype = np.dtype(f"M8[{new_unit}]")
2325+
new_dtype = get_supported_dtype(new_dtype)
23302326
data = astype_overflowsafe(data, dtype=new_dtype, copy=False)
2331-
data_unit = get_unit_from_dtype(new_dtype)
23322327
copy = False
23332328

23342329
if data.dtype.byteorder == ">":
@@ -2346,6 +2341,7 @@ def _construct_from_dt64_naive(
23462341
if data.ndim > 1:
23472342
data = data.ravel()
23482343

2344+
data_unit = get_unit_from_dtype(new_dtype)
23492345
data = tzconversion.tz_localize_to_utc(
23502346
data.view("i8"), tz, ambiguous=ambiguous, creso=data_unit
23512347
)
@@ -2552,7 +2548,7 @@ def _validate_dt64_dtype(dtype):
25522548

25532549
if (
25542550
isinstance(dtype, np.dtype)
2555-
and (dtype.kind != "M" or not is_supported_unit(get_unit_from_dtype(dtype)))
2551+
and (dtype.kind != "M" or not is_supported_dtype(dtype))
25562552
) or not isinstance(dtype, (np.dtype, DatetimeTZDtype)):
25572553
raise ValueError(
25582554
f"Unexpected value for 'dtype': '{dtype}'. "

pandas/core/arrays/masked.py

+2-7
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@
1515
lib,
1616
missing as libmissing,
1717
)
18-
from pandas._libs.tslibs import (
19-
get_unit_from_dtype,
20-
is_supported_unit,
21-
)
18+
from pandas._libs.tslibs import is_supported_dtype
2219
from pandas._typing import (
2320
ArrayLike,
2421
AstypeArg,
@@ -876,9 +873,7 @@ def _maybe_mask_result(
876873

877874
return BooleanArray(result, mask, copy=False)
878875

879-
elif lib.is_np_dtype(result.dtype, "m") and is_supported_unit(
880-
get_unit_from_dtype(result.dtype)
881-
):
876+
elif lib.is_np_dtype(result.dtype, "m") and is_supported_dtype(result.dtype):
882877
# e.g. test_numeric_arr_mul_tdscalar_numexpr_path
883878
from pandas.core.arrays import TimedeltaArray
884879

pandas/core/arrays/numpy_.py

+2-7
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@
88
import numpy as np
99

1010
from pandas._libs import lib
11-
from pandas._libs.tslibs import (
12-
get_unit_from_dtype,
13-
is_supported_unit,
14-
)
11+
from pandas._libs.tslibs import is_supported_dtype
1512
from pandas.compat.numpy import function as nv
1613

1714
from pandas.core.dtypes.astype import astype_array
@@ -553,9 +550,7 @@ def _cmp_method(self, other, op):
553550
def _wrap_ndarray_result(self, result: np.ndarray):
554551
# If we have timedelta64[ns] result, return a TimedeltaArray instead
555552
# of a NumpyExtensionArray
556-
if result.dtype.kind == "m" and is_supported_unit(
557-
get_unit_from_dtype(result.dtype)
558-
):
553+
if result.dtype.kind == "m" and is_supported_dtype(result.dtype):
559554
from pandas.core.arrays import TimedeltaArray
560555

561556
return TimedeltaArray._simple_new(result, dtype=result.dtype)

pandas/core/arrays/timedeltas.py

+6-11
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,9 @@
1919
Tick,
2020
Timedelta,
2121
astype_overflowsafe,
22-
get_supported_reso,
23-
get_unit_from_dtype,
22+
get_supported_dtype,
2423
iNaT,
25-
is_supported_unit,
26-
npy_unit_to_abbrev,
24+
is_supported_dtype,
2725
periods_per_second,
2826
)
2927
from pandas._libs.tslibs.conversion import cast_from_unit_vectorized
@@ -352,7 +350,7 @@ def astype(self, dtype, copy: bool = True):
352350
return self.copy()
353351
return self
354352

355-
if is_supported_unit(get_unit_from_dtype(dtype)):
353+
if is_supported_dtype(dtype):
356354
# unit conversion e.g. timedelta64[s]
357355
res_values = astype_overflowsafe(self._ndarray, dtype, copy=False)
358356
return type(self)._simple_new(
@@ -1064,12 +1062,9 @@ def sequence_to_td64ns(
10641062
copy = False
10651063

10661064
elif lib.is_np_dtype(data.dtype, "m"):
1067-
data_unit = get_unit_from_dtype(data.dtype)
1068-
if not is_supported_unit(data_unit):
1065+
if not is_supported_dtype(data.dtype):
10691066
# cast to closest supported unit, i.e. s or ns
1070-
new_reso = get_supported_reso(data_unit)
1071-
new_unit = npy_unit_to_abbrev(new_reso)
1072-
new_dtype = np.dtype(f"m8[{new_unit}]")
1067+
new_dtype = get_supported_dtype(data.dtype)
10731068
data = astype_overflowsafe(data, dtype=new_dtype, copy=False)
10741069
copy = False
10751070

@@ -1173,7 +1168,7 @@ def _validate_td64_dtype(dtype) -> DtypeObj:
11731168

11741169
if not lib.is_np_dtype(dtype, "m"):
11751170
raise ValueError(f"dtype '{dtype}' is invalid, should be np.timedelta64 dtype")
1176-
elif not is_supported_unit(get_unit_from_dtype(dtype)):
1171+
elif not is_supported_dtype(dtype):
11771172
raise ValueError("Supported timedelta64 resolutions are 's', 'ms', 'us', 'ns'")
11781173

11791174
return dtype

pandas/core/construction.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
from pandas._libs import lib
2525
from pandas._libs.tslibs import (
2626
Period,
27-
get_unit_from_dtype,
28-
is_supported_unit,
27+
get_supported_dtype,
28+
is_supported_dtype,
2929
)
3030
from pandas._typing import (
3131
AnyArrayLike,
@@ -370,9 +370,9 @@ def array(
370370
# 1. datetime64[ns,us,ms,s]
371371
# 2. timedelta64[ns,us,ms,s]
372372
# so that a DatetimeArray is returned.
373-
if lib.is_np_dtype(dtype, "M") and is_supported_unit(get_unit_from_dtype(dtype)):
373+
if lib.is_np_dtype(dtype, "M") and is_supported_dtype(dtype):
374374
return DatetimeArray._from_sequence(data, dtype=dtype, copy=copy)
375-
if lib.is_np_dtype(dtype, "m") and is_supported_unit(get_unit_from_dtype(dtype)):
375+
if lib.is_np_dtype(dtype, "m") and is_supported_dtype(dtype):
376376
return TimedeltaArray._from_sequence(data, dtype=dtype, copy=copy)
377377

378378
elif lib.is_np_dtype(dtype, "mM"):
@@ -490,12 +490,14 @@ def ensure_wrapped_if_datetimelike(arr):
490490
if arr.dtype.kind == "M":
491491
from pandas.core.arrays import DatetimeArray
492492

493-
return DatetimeArray._from_sequence(arr)
493+
dtype = get_supported_dtype(arr.dtype)
494+
return DatetimeArray._from_sequence(arr, dtype=dtype)
494495

495496
elif arr.dtype.kind == "m":
496497
from pandas.core.arrays import TimedeltaArray
497498

498-
return TimedeltaArray._from_sequence(arr)
499+
dtype = get_supported_dtype(arr.dtype)
500+
return TimedeltaArray._from_sequence(arr, dtype=dtype)
499501

500502
return arr
501503

pandas/core/dtypes/cast.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,7 @@
3636
OutOfBoundsTimedelta,
3737
Timedelta,
3838
Timestamp,
39-
get_unit_from_dtype,
40-
is_supported_unit,
39+
is_supported_dtype,
4140
)
4241
from pandas._libs.tslibs.timedeltas import array_to_timedelta64
4342
from pandas.errors import (
@@ -1266,8 +1265,7 @@ def _ensure_nanosecond_dtype(dtype: DtypeObj) -> None:
12661265
pass
12671266

12681267
elif dtype.kind in "mM":
1269-
reso = get_unit_from_dtype(dtype)
1270-
if not is_supported_unit(reso):
1268+
if not is_supported_dtype(dtype):
12711269
# pre-2.0 we would silently swap in nanos for lower-resolutions,
12721270
# raise for above-nano resolutions
12731271
if dtype.name in ["datetime64", "timedelta64"]:

0 commit comments

Comments
 (0)