Skip to content

Commit 2af3e8d

Browse files
feat: Warn on incompatible TimeUnit in pandas<2 (#3007)
Co-authored-by: Dan Redding <[email protected]>
1 parent aa7a7a7 commit 2af3e8d

File tree

2 files changed

+39
-2
lines changed

2 files changed

+39
-2
lines changed

narwhals/_pandas_like/utils.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@
1616
SECONDS_PER_DAY,
1717
US_PER_SECOND,
1818
)
19+
from narwhals._exceptions import issue_warning
1920
from narwhals._utils import (
2021
Implementation,
2122
Version,
2223
_DeferredIterable,
2324
check_columns_exist,
2425
isinstance_or_issubclass,
26+
requires,
2527
)
2628
from narwhals.exceptions import ShapeError
2729

@@ -493,10 +495,21 @@ def narwhals_to_native_dtype( # noqa: C901, PLR0912
493495
if into_pd_type := NW_TO_PD_DTYPES_BACKEND.get(base_type):
494496
return into_pd_type[dtype_backend]
495497
if isinstance_or_issubclass(dtype, dtypes.Datetime):
496-
# Pandas does not support "ms" or "us" time units before version 2.0
497498
if is_pandas_or_modin(implementation) and PANDAS_VERSION < (
498499
2,
499500
): # pragma: no cover
501+
if isinstance(dtype, dtypes.Datetime) and dtype.time_unit != "ns":
502+
found = requires._unparse_version(PANDAS_VERSION)
503+
available = f"available in 'pandas>=2.0', found version {found!r}."
504+
changelog_url = "https://pandas.pydata.org/docs/dev/whatsnew/v2.0.0.html#construction-with-datetime64-or-timedelta64-dtype-with-unsupported-resolution"
505+
msg = (
506+
f"`nw.Datetime(time_unit={dtype.time_unit!r})` is only {available}\n"
507+
"Narwhals has fallen back to using `time_unit='ns'` to avoid an error.\n\n"
508+
"Hint: to avoid this warning, consider either:\n"
509+
f"- Upgrading pandas: {changelog_url}\n"
510+
f"- Using a bare `nw.Datetime`, if this precision is not important"
511+
)
512+
issue_warning(msg, UserWarning)
500513
dt_time_unit = "ns"
501514
else:
502515
dt_time_unit = dtype.time_unit

tests/dtypes_test.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import enum
4+
from contextlib import AbstractContextManager, nullcontext as does_not_warn
45
from datetime import datetime, timedelta, timezone
56
from typing import TYPE_CHECKING, Any, Literal
67

@@ -17,7 +18,7 @@
1718
from collections.abc import Iterable
1819

1920
from narwhals.typing import IntoSeries, NonNestedDType
20-
from tests.utils import Constructor
21+
from tests.utils import Constructor, ConstructorPandasLike
2122

2223

2324
@pytest.mark.parametrize("time_unit", ["us", "ns", "ms"])
@@ -558,3 +559,26 @@ def test_dtype_base_type_nested() -> None:
558559
assert nw.Array.base_type() is nw.Array(nw.String, 2).base_type()
559560
assert nw.Struct.base_type() is nw.Struct({"a": nw.Boolean}).base_type()
560561
assert nw.Enum.base_type() is nw.Enum(["beluga", "narwhal"]).base_type()
562+
563+
564+
@pytest.mark.parametrize(
565+
("dtype", "context"),
566+
[
567+
(nw.Datetime("ns"), does_not_warn()),
568+
(nw.Datetime, does_not_warn()),
569+
(nw.Datetime(), pytest.warns(UserWarning, match="time_unit")),
570+
(nw.Datetime("us"), pytest.warns(UserWarning, match="time_unit='us'")),
571+
(nw.Datetime("s"), pytest.warns(UserWarning, match="time_unit='s'")),
572+
],
573+
)
574+
def test_pandas_datetime_ignored_time_unit_warns(
575+
constructor_pandas_like: ConstructorPandasLike,
576+
dtype: nw.Datetime | type[nw.Datetime],
577+
context: AbstractContextManager[Any],
578+
) -> None:
579+
data = {"a": [datetime(2001, 1, 1), None, datetime(2001, 1, 3)]}
580+
expr = nw.col("a").cast(dtype)
581+
df = nw.from_native(constructor_pandas_like(data))
582+
ctx = does_not_warn() if PANDAS_VERSION >= (2,) else context
583+
with ctx:
584+
df.select(expr)

0 commit comments

Comments
 (0)