From 6330a1f01ebc4622883dffccff75268c9d0028bf Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 17 Nov 2021 18:33:32 -0800 Subject: [PATCH 1/2] BUG: Period incorrectly allowing np.timedelta64('NaT') --- doc/source/whatsnew/v1.4.0.rst | 2 +- pandas/_libs/tslibs/period.pyx | 16 +++++++++---- .../tests/arrays/period/test_constructors.py | 24 +++++++++++++++++++ pandas/tests/scalar/period/test_period.py | 10 ++++++++ 4 files changed, 46 insertions(+), 6 deletions(-) diff --git a/doc/source/whatsnew/v1.4.0.rst b/doc/source/whatsnew/v1.4.0.rst index 2456406f0eca3..8c39c57801734 100644 --- a/doc/source/whatsnew/v1.4.0.rst +++ b/doc/source/whatsnew/v1.4.0.rst @@ -650,7 +650,7 @@ Period ^^^^^^ - Bug in adding a :class:`Period` object to a ``np.timedelta64`` object incorrectly raising ``TypeError`` (:issue:`44182`) - Bug in :meth:`PeriodIndex.to_timestamp` when the index has ``freq="B"`` inferring ``freq="D"`` for its result instead of ``freq="B"`` (:issue:`44105`) -- +- Bug in :class:`Period` constructor incorrectly allowing ``np.timedelta64("NaT")`` (:issue:`??`) Plotting ^^^^^^^^ diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index f594e0a8bdafd..67696f9740ea1 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -104,7 +104,7 @@ from pandas._libs.tslibs.nattype cimport ( _nat_scalar_rules, c_NaT as NaT, c_nat_strings as nat_strings, - is_null_datetimelike, + checknull_with_nat, ) from pandas._libs.tslibs.offsets cimport ( BaseOffset, @@ -1459,10 +1459,13 @@ def extract_ordinals(ndarray[object] values, freq) -> np.ndarray: for i in range(n): p = values[i] - if is_null_datetimelike(p): + if checknull_with_nat(p): ordinals[i] = NPY_NAT elif util.is_integer_object(p): - raise TypeError(p) + if p == NPY_NAT: + ordinals[i] = NPY_NAT + else: + raise TypeError(p) else: try: ordinals[i] = p.ordinal @@ -2473,14 +2476,17 @@ class Period(_Period): converted = other.asfreq(freq) ordinal = converted.ordinal - elif is_null_datetimelike(value) or (isinstance(value, str) and - value in nat_strings): + elif checknull_with_nat(value) or (isinstance(value, str) and + value in nat_strings): # explicit str check is necessary to avoid raising incorrectly # if we have a non-hashable value. ordinal = NPY_NAT elif isinstance(value, str) or util.is_integer_object(value): if util.is_integer_object(value): + if value == NPY_NAT: + value = "NaT" + value = str(value) value = value.upper() dt, reso = parse_time_string(value, freq) diff --git a/pandas/tests/arrays/period/test_constructors.py b/pandas/tests/arrays/period/test_constructors.py index 52543d91e8f2a..2ed54e3ed1f79 100644 --- a/pandas/tests/arrays/period/test_constructors.py +++ b/pandas/tests/arrays/period/test_constructors.py @@ -96,3 +96,27 @@ def test_from_sequence_disallows_i8(): with pytest.raises(TypeError, match=msg): PeriodArray._from_sequence(list(arr.asi8), dtype=arr.dtype) + + +def test_from_td64nat_sequence_raises(): + td = pd.NaT.to_numpy("m8[ns]") + + dtype = pd.period_range("2005-01-01", periods=3, freq="D").dtype + + arr = np.array([None], dtype=object) + arr[0] = td + + msg = "Value must be Period, string, integer, or datetime" + with pytest.raises(ValueError, match=msg): + PeriodArray._from_sequence(arr, dtype=dtype) + + with pytest.raises(ValueError, match=msg): + pd.PeriodIndex(arr, dtype=dtype) + with pytest.raises(ValueError, match=msg): + pd.Index(arr, dtype=dtype) + with pytest.raises(ValueError, match=msg): + pd.array(arr, dtype=dtype) + with pytest.raises(ValueError, match=msg): + pd.Series(arr, dtype=dtype) + with pytest.raises(ValueError, match=msg): + pd.DataFrame(arr, dtype=dtype) diff --git a/pandas/tests/scalar/period/test_period.py b/pandas/tests/scalar/period/test_period.py index cd1bf21753249..a855227575f1a 100644 --- a/pandas/tests/scalar/period/test_period.py +++ b/pandas/tests/scalar/period/test_period.py @@ -40,6 +40,16 @@ class TestPeriodConstruction: + def test_from_td64nat_raises(self): + td = NaT.to_numpy("m8[ns]") + + msg = "Value must be Period, string, integer, or datetime" + with pytest.raises(ValueError, match=msg): + Period(td) + + with pytest.raises(ValueError, match=msg): + Period(td, freq="D") + def test_construction(self): i1 = Period("1/1/2005", freq="M") i2 = Period("Jan 2005") From 1675004c25d7d64c072cadeb0ead5e9df9c0ee4b Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 17 Nov 2021 18:34:35 -0800 Subject: [PATCH 2/2] GH refs --- doc/source/whatsnew/v1.4.0.rst | 3 ++- pandas/tests/arrays/period/test_constructors.py | 1 + pandas/tests/scalar/period/test_period.py | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.4.0.rst b/doc/source/whatsnew/v1.4.0.rst index 8c39c57801734..16fd6c7268c7b 100644 --- a/doc/source/whatsnew/v1.4.0.rst +++ b/doc/source/whatsnew/v1.4.0.rst @@ -650,7 +650,8 @@ Period ^^^^^^ - Bug in adding a :class:`Period` object to a ``np.timedelta64`` object incorrectly raising ``TypeError`` (:issue:`44182`) - Bug in :meth:`PeriodIndex.to_timestamp` when the index has ``freq="B"`` inferring ``freq="D"`` for its result instead of ``freq="B"`` (:issue:`44105`) -- Bug in :class:`Period` constructor incorrectly allowing ``np.timedelta64("NaT")`` (:issue:`??`) +- Bug in :class:`Period` constructor incorrectly allowing ``np.timedelta64("NaT")`` (:issue:`44507`) +- Plotting ^^^^^^^^ diff --git a/pandas/tests/arrays/period/test_constructors.py b/pandas/tests/arrays/period/test_constructors.py index 2ed54e3ed1f79..cf9749058d1d1 100644 --- a/pandas/tests/arrays/period/test_constructors.py +++ b/pandas/tests/arrays/period/test_constructors.py @@ -99,6 +99,7 @@ def test_from_sequence_disallows_i8(): def test_from_td64nat_sequence_raises(): + # GH#44507 td = pd.NaT.to_numpy("m8[ns]") dtype = pd.period_range("2005-01-01", periods=3, freq="D").dtype diff --git a/pandas/tests/scalar/period/test_period.py b/pandas/tests/scalar/period/test_period.py index a855227575f1a..f35033115d2fc 100644 --- a/pandas/tests/scalar/period/test_period.py +++ b/pandas/tests/scalar/period/test_period.py @@ -41,6 +41,7 @@ class TestPeriodConstruction: def test_from_td64nat_raises(self): + # GH#44507 td = NaT.to_numpy("m8[ns]") msg = "Value must be Period, string, integer, or datetime"