Skip to content

BUG: Period incorrectly allowing np.timedelta64('NaT') #44507

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

Merged
merged 4 commits into from
Nov 20, 2021
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions doc/source/whatsnew/v1.4.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,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:`44507`)
-

Plotting
Expand Down
16 changes: 11 additions & 5 deletions pandas/_libs/tslibs/period.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
25 changes: 25 additions & 0 deletions pandas/tests/arrays/period/test_constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,28 @@ 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():
# GH#44507
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)
11 changes: 11 additions & 0 deletions pandas/tests/scalar/period/test_period.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@


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"
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")
Expand Down