Skip to content

FIX: Reject any non-array, non-ns datetimes and timedeltas #48360

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 9 additions & 32 deletions pandas/core/dtypes/cast.py
Original file line number Diff line number Diff line change
@@ -1308,7 +1308,7 @@ def maybe_cast_to_datetime(
# TODO: _from_sequence would raise ValueError in cases where
# _ensure_nanosecond_dtype raises TypeError
dtype = cast(np.dtype, dtype)
dtype = _ensure_nanosecond_dtype(dtype)
_ensure_nanosecond_dtype(dtype)
res = TimedeltaArray._from_sequence(value, dtype=dtype)
return res

@@ -1319,7 +1319,7 @@ def maybe_cast_to_datetime(
vdtype = getattr(value, "dtype", None)

if is_datetime64 or is_datetime64tz:
dtype = _ensure_nanosecond_dtype(dtype)
_ensure_nanosecond_dtype(dtype)

value = np.array(value, copy=False)

@@ -1437,17 +1437,9 @@ def sanitize_to_nanoseconds(values: np.ndarray, copy: bool = False) -> np.ndarra
return values


def _ensure_nanosecond_dtype(dtype: DtypeObj) -> DtypeObj:
def _ensure_nanosecond_dtype(dtype: DtypeObj) -> None:
"""
Convert dtypes with granularity less than nanosecond to nanosecond
>>> _ensure_nanosecond_dtype(np.dtype("M8[s]"))
dtype('<M8[ns]')
>>> _ensure_nanosecond_dtype(np.dtype("m8[ps]"))
Traceback (most recent call last):
...
TypeError: cannot convert timedeltalike to dtype [timedelta64[ps]]
Reject datetime/timedelta dtypes with granularity different from ns
"""
msg = (
f"The '{dtype.name}' dtype has no unit. "
@@ -1459,28 +1451,13 @@ def _ensure_nanosecond_dtype(dtype: DtypeObj) -> DtypeObj:

if not isinstance(dtype, np.dtype):
# i.e. datetime64tz
pass
return

elif dtype.kind == "M" and dtype != DT64NS_DTYPE:
# pandas supports dtype whose granularity is less than [ns]
# e.g., [ps], [fs], [as]
if dtype <= np.dtype("M8[ns]"):
if dtype.name == "datetime64":
raise ValueError(msg)
dtype = DT64NS_DTYPE
else:
raise TypeError(f"cannot convert datetimelike to dtype [{dtype}]")
if dtype.name in ("datetime64", "timedelta64"):
raise ValueError(msg)

elif dtype.kind == "m" and dtype != TD64NS_DTYPE:
# pandas supports dtype whose granularity is less than [ns]
# e.g., [ps], [fs], [as]
if dtype <= np.dtype("m8[ns]"):
if dtype.name == "timedelta64":
raise ValueError(msg)
dtype = TD64NS_DTYPE
else:
raise TypeError(f"cannot convert timedeltalike to dtype [{dtype}]")
return dtype
if dtype not in (TD64NS_DTYPE, DT64NS_DTYPE):
raise TypeError("Only [ns] granularity is supported for timedelta and datetime")


# TODO: other value-dependent functions to standardize here include
28 changes: 10 additions & 18 deletions pandas/tests/series/test_constructors.py
Original file line number Diff line number Diff line change
@@ -1578,6 +1578,14 @@ def test_convert_non_ns(self):
expected = Series(date_range("20130101 00:00:01", periods=3, freq="s"))
tm.assert_series_equal(ser, expected)

# try to construct from a sequence asking for non-ns timedelta64
with pytest.raises(TypeError, match=r"Only \[ns\] granularity is supported"):
Series([1000000, 200000, 3000000], dtype="timedelta64[s]")

# try to construct from a sequence asking for non-ns datetime64
with pytest.raises(TypeError, match=r"Only \[ns\] granularity is supported"):
Series([1000000, 200000, 3000000], dtype="datetime64[s]")

@pytest.mark.parametrize(
"index",
[
@@ -1642,8 +1650,8 @@ def test_constructor_generic_timestamp_no_frequency(self, dtype, request):
@pytest.mark.parametrize(
"dtype,msg",
[
("m8[ps]", "cannot convert timedeltalike"),
("M8[ps]", "cannot convert datetimelike"),
(r"m8[ps]", r"Only \[ns\] granularity is supported"),
(r"M8[ps]", r"Only \[ns\] granularity is supported"),
],
)
def test_constructor_generic_timestamp_bad_frequency(self, dtype, msg):
@@ -1886,22 +1894,6 @@ def test_constructor_dtype_timedelta_alternative_construct(self):
expected = Series(pd.to_timedelta([1000000, 200000, 3000000], unit="ns"))
tm.assert_series_equal(result, expected)

def test_constructor_dtype_timedelta_ns_s(self):
# GH#35465
result = Series([1000000, 200000, 3000000], dtype="timedelta64[ns]")
expected = Series([1000000, 200000, 3000000], dtype="timedelta64[s]")
tm.assert_series_equal(result, expected)

def test_constructor_dtype_timedelta_ns_s_astype_int64(self):
# GH#35465
result = Series([1000000, 200000, 3000000], dtype="timedelta64[ns]").astype(
"int64"
)
expected = Series([1000000, 200000, 3000000], dtype="timedelta64[s]").astype(
"int64"
)
tm.assert_series_equal(result, expected)

@pytest.mark.filterwarnings(
"ignore:elementwise comparison failed:DeprecationWarning"
)