diff --git a/crates/polars-time/src/chunkedarray/date.rs b/crates/polars-time/src/chunkedarray/date.rs index f41e4c14124f..6ff615d911e4 100644 --- a/crates/polars-time/src/chunkedarray/date.rs +++ b/crates/polars-time/src/chunkedarray/date.rs @@ -91,7 +91,7 @@ pub trait DateMethods: AsDate { .map(|((y, m), d)| { if let (Some(y), Some(m), Some(d)) = (y, m, d) { let Some(ns) = NaiveDate::from_ymd_opt(y, m as u32, d as u32) else { - panic!("Invalid date components ({}, {}, {}) supplied.", y, m, d) + panic!("Invalid date components ({}, {}, {}) supplied", y, m, d) }; Some(ns.num_days_from_ce() - EPOCH_DAYS_FROM_CE) } else { diff --git a/crates/polars-time/src/chunkedarray/datetime.rs b/crates/polars-time/src/chunkedarray/datetime.rs index e7358915acb2..8eedffdd6250 100644 --- a/crates/polars-time/src/chunkedarray/datetime.rs +++ b/crates/polars-time/src/chunkedarray/datetime.rs @@ -180,16 +180,13 @@ pub trait DatetimeMethods: AsDatetime { (y, m, d, h, mnt, s, ns) { let Some(t) = NaiveDate::from_ymd_opt(y, m as u32, d as u32) else { - panic!( - "Invalid datetime components ({}, {}, {}, {}, {}, {}, {}) supplied.", - y, m, d, h, mnt, s, ns - ) + panic!("Invalid date components ({}, {}, {}) supplied", y, m, d) }; let Some(ndt) = t.and_hms_nano_opt(h as u32, mnt as u32, s as u32, ns as u32) else { panic!( - "Invalid datetime components ({}, {}, {}, {}, {}, {}, {}) supplied.", - y, m, d, h, mnt, s, ns + "Invalid time components ({}, {}, {}, {}) supplied", + h, mnt, s, ns ) }; Some(match time_unit { diff --git a/py-polars/tests/unit/functions/as_datatype/test_datetime.py b/py-polars/tests/unit/functions/as_datatype/test_datetime.py index a4070984132b..8ab725b86de9 100644 --- a/py-polars/tests/unit/functions/as_datatype/test_datetime.py +++ b/py-polars/tests/unit/functions/as_datatype/test_datetime.py @@ -42,7 +42,7 @@ def test_date_datetime() -> None: ) def test_date_invalid_component(components: list[int]) -> None: y, m, d = components - msg = rf"Invalid datetime components \({y}, {m}, {d}, 0, 0, 0, 0\) supplied." + msg = rf"Invalid date components \({y}, {m}, {d}\) supplied" with pytest.raises(PanicException, match=msg): pl.select(pl.date(*components)) @@ -53,16 +53,28 @@ def test_date_invalid_component(components: list[int]) -> None: [2025, 13, 1, 0, 0, 0, 0], [2025, 1, 32, 0, 0, 0, 0], [2025, 2, 29, 0, 0, 0, 0], + ], +) +def test_datetime_invalid_date_component(components: list[int]) -> None: + y, m, d = components[0:3] + msg = rf"Invalid date components \({y}, {m}, {d}\) supplied" + with pytest.raises(PanicException, match=msg): + pl.select(pl.datetime(*components)) + + +@pytest.mark.parametrize( + "components", + [ [2025, 1, 1, 25, 0, 0, 0], [2025, 1, 1, 0, 60, 0, 0], [2025, 1, 1, 0, 0, 60, 0], [2025, 1, 1, 0, 0, 0, 2_000_000], ], ) -def test_datetime_invalid_component(components: list[int]) -> None: +def test_datetime_invalid_time_component(components: list[int]) -> None: y, m, d, h, mnt, s, us = components ns = us * 1_000 - msg = rf"Invalid datetime components \({y}, {m}, {d}, {h}, {mnt}, {s}, {ns}\) supplied." + msg = rf"Invalid time components \({h}, {mnt}, {s}, {ns}\) supplied" with pytest.raises(PanicException, match=msg): pl.select(pl.datetime(*components)) diff --git a/py-polars/tests/unit/operations/namespaces/temporal/test_replace.py b/py-polars/tests/unit/operations/namespaces/temporal/test_replace.py index 4d144ba2662c..ca493ff644e3 100644 --- a/py-polars/tests/unit/operations/namespaces/temporal/test_replace.py +++ b/py-polars/tests/unit/operations/namespaces/temporal/test_replace.py @@ -6,7 +6,7 @@ import pytest import polars as pl -from polars.exceptions import ComputeError +from polars.exceptions import ComputeError, PanicException from polars.testing import assert_frame_equal, assert_series_equal if TYPE_CHECKING: @@ -298,3 +298,58 @@ def test_replace_preserve_tu_and_tz(tu: TimeUnit, tzinfo: str) -> None: result = s.dt.replace(year=2000) assert result.dtype.time_unit == tu # type: ignore[attr-defined] assert result.dtype.time_zone == tzinfo # type: ignore[attr-defined] + + +def test_replace_date_invalid_components() -> None: + df = pl.DataFrame({"a": [date(2025, 1, 1)]}) + + with pytest.raises( + PanicException, match=r"Invalid date components \(2025, 13, 1\) supplied" + ): + df.select(pl.col("a").dt.replace(month=13)) + with pytest.raises( + PanicException, match=r"Invalid date components \(2025, 1, 32\) supplied" + ): + df.select(pl.col("a").dt.replace(day=32)) + + +def test_replace_datetime_invalid_date_components() -> None: + df = pl.DataFrame({"a": [datetime(2025, 1, 1)]}) + + with pytest.raises( + PanicException, match=r"Invalid date components \(2025, 13, 1\) supplied" + ): + df.select(pl.col("a").dt.replace(month=13)) + with pytest.raises( + PanicException, match=r"Invalid date components \(2025, 1, 32\) supplied" + ): + df.select(pl.col("a").dt.replace(day=32)) + + +def test_replace_datetime_invalid_time_components() -> None: + df = pl.DataFrame({"a": [datetime(2025, 1, 1)]}) + + # hour + with pytest.raises( + PanicException, match=r"Invalid time components \(25, 0, 0, 0\) supplied" + ): + df.select(pl.col("a").dt.replace(hour=25)) + + # minute + with pytest.raises( + PanicException, match=r"Invalid time components \(0, 61, 0, 0\) supplied" + ): + df.select(pl.col("a").dt.replace(minute=61)) + + # second + with pytest.raises( + PanicException, match=r"Invalid time components \(0, 0, 61, 0\) supplied" + ): + df.select(pl.col("a").dt.replace(second=61)) + + # microsecond + with pytest.raises( + PanicException, + match=r"Invalid time components \(0, 0, 0, 2000000000\) supplied", + ): + df.select(pl.col("a").dt.replace(microsecond=2_000_000))