710 lines
20 KiB
Python
710 lines
20 KiB
Python
|
from datetime import (
|
||
|
datetime,
|
||
|
timedelta,
|
||
|
)
|
||
|
import operator
|
||
|
|
||
|
import numpy as np
|
||
|
import pytest
|
||
|
import pytz
|
||
|
|
||
|
from pandas._libs.tslibs import iNaT
|
||
|
from pandas.compat.numpy import np_version_gte1p24p3
|
||
|
|
||
|
from pandas import (
|
||
|
DatetimeIndex,
|
||
|
DatetimeTZDtype,
|
||
|
Index,
|
||
|
NaT,
|
||
|
Period,
|
||
|
Series,
|
||
|
Timedelta,
|
||
|
TimedeltaIndex,
|
||
|
Timestamp,
|
||
|
isna,
|
||
|
offsets,
|
||
|
)
|
||
|
import pandas._testing as tm
|
||
|
from pandas.core import roperator
|
||
|
from pandas.core.arrays import (
|
||
|
DatetimeArray,
|
||
|
PeriodArray,
|
||
|
TimedeltaArray,
|
||
|
)
|
||
|
|
||
|
|
||
|
class TestNaTFormatting:
|
||
|
def test_repr(self):
|
||
|
assert repr(NaT) == "NaT"
|
||
|
|
||
|
def test_str(self):
|
||
|
assert str(NaT) == "NaT"
|
||
|
|
||
|
def test_isoformat(self):
|
||
|
assert NaT.isoformat() == "NaT"
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"nat,idx",
|
||
|
[
|
||
|
(Timestamp("NaT"), DatetimeArray),
|
||
|
(Timedelta("NaT"), TimedeltaArray),
|
||
|
(Period("NaT", freq="M"), PeriodArray),
|
||
|
],
|
||
|
)
|
||
|
def test_nat_fields(nat, idx):
|
||
|
for field in idx._field_ops:
|
||
|
# weekday is a property of DTI, but a method
|
||
|
# on NaT/Timestamp for compat with datetime
|
||
|
if field == "weekday":
|
||
|
continue
|
||
|
|
||
|
result = getattr(NaT, field)
|
||
|
assert np.isnan(result)
|
||
|
|
||
|
result = getattr(nat, field)
|
||
|
assert np.isnan(result)
|
||
|
|
||
|
for field in idx._bool_ops:
|
||
|
result = getattr(NaT, field)
|
||
|
assert result is False
|
||
|
|
||
|
result = getattr(nat, field)
|
||
|
assert result is False
|
||
|
|
||
|
|
||
|
def test_nat_vector_field_access():
|
||
|
idx = DatetimeIndex(["1/1/2000", None, None, "1/4/2000"])
|
||
|
|
||
|
for field in DatetimeArray._field_ops:
|
||
|
# weekday is a property of DTI, but a method
|
||
|
# on NaT/Timestamp for compat with datetime
|
||
|
if field == "weekday":
|
||
|
continue
|
||
|
|
||
|
result = getattr(idx, field)
|
||
|
expected = Index([getattr(x, field) for x in idx])
|
||
|
tm.assert_index_equal(result, expected)
|
||
|
|
||
|
ser = Series(idx)
|
||
|
|
||
|
for field in DatetimeArray._field_ops:
|
||
|
# weekday is a property of DTI, but a method
|
||
|
# on NaT/Timestamp for compat with datetime
|
||
|
if field == "weekday":
|
||
|
continue
|
||
|
|
||
|
result = getattr(ser.dt, field)
|
||
|
expected = [getattr(x, field) for x in idx]
|
||
|
tm.assert_series_equal(result, Series(expected))
|
||
|
|
||
|
for field in DatetimeArray._bool_ops:
|
||
|
result = getattr(ser.dt, field)
|
||
|
expected = [getattr(x, field) for x in idx]
|
||
|
tm.assert_series_equal(result, Series(expected))
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize("klass", [Timestamp, Timedelta, Period])
|
||
|
@pytest.mark.parametrize(
|
||
|
"value", [None, np.nan, iNaT, float("nan"), NaT, "NaT", "nat", "", "NAT"]
|
||
|
)
|
||
|
def test_identity(klass, value):
|
||
|
assert klass(value) is NaT
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize("klass", [Timestamp, Timedelta])
|
||
|
@pytest.mark.parametrize("method", ["round", "floor", "ceil"])
|
||
|
@pytest.mark.parametrize("freq", ["s", "5s", "min", "5min", "h", "5h"])
|
||
|
def test_round_nat(klass, method, freq):
|
||
|
# see gh-14940
|
||
|
ts = klass("nat")
|
||
|
|
||
|
round_method = getattr(ts, method)
|
||
|
assert round_method(freq) is ts
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"method",
|
||
|
[
|
||
|
"astimezone",
|
||
|
"combine",
|
||
|
"ctime",
|
||
|
"dst",
|
||
|
"fromordinal",
|
||
|
"fromtimestamp",
|
||
|
"fromisocalendar",
|
||
|
"isocalendar",
|
||
|
"strftime",
|
||
|
"strptime",
|
||
|
"time",
|
||
|
"timestamp",
|
||
|
"timetuple",
|
||
|
"timetz",
|
||
|
"toordinal",
|
||
|
"tzname",
|
||
|
"utcfromtimestamp",
|
||
|
"utcnow",
|
||
|
"utcoffset",
|
||
|
"utctimetuple",
|
||
|
"timestamp",
|
||
|
],
|
||
|
)
|
||
|
def test_nat_methods_raise(method):
|
||
|
# see gh-9513, gh-17329
|
||
|
msg = f"NaTType does not support {method}"
|
||
|
|
||
|
with pytest.raises(ValueError, match=msg):
|
||
|
getattr(NaT, method)()
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize("method", ["weekday", "isoweekday"])
|
||
|
def test_nat_methods_nan(method):
|
||
|
# see gh-9513, gh-17329
|
||
|
assert np.isnan(getattr(NaT, method)())
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"method", ["date", "now", "replace", "today", "tz_convert", "tz_localize"]
|
||
|
)
|
||
|
def test_nat_methods_nat(method):
|
||
|
# see gh-8254, gh-9513, gh-17329
|
||
|
assert getattr(NaT, method)() is NaT
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"get_nat", [lambda x: NaT, lambda x: Timedelta(x), lambda x: Timestamp(x)]
|
||
|
)
|
||
|
def test_nat_iso_format(get_nat):
|
||
|
# see gh-12300
|
||
|
assert get_nat("NaT").isoformat() == "NaT"
|
||
|
assert get_nat("NaT").isoformat(timespec="nanoseconds") == "NaT"
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"klass,expected",
|
||
|
[
|
||
|
(Timestamp, ["normalize", "to_julian_date", "to_period", "unit"]),
|
||
|
(
|
||
|
Timedelta,
|
||
|
[
|
||
|
"components",
|
||
|
"resolution_string",
|
||
|
"to_pytimedelta",
|
||
|
"to_timedelta64",
|
||
|
"unit",
|
||
|
"view",
|
||
|
],
|
||
|
),
|
||
|
],
|
||
|
)
|
||
|
def test_missing_public_nat_methods(klass, expected):
|
||
|
# see gh-17327
|
||
|
#
|
||
|
# NaT should have *most* of the Timestamp and Timedelta methods.
|
||
|
# Here, we check which public methods NaT does not have. We
|
||
|
# ignore any missing private methods.
|
||
|
nat_names = dir(NaT)
|
||
|
klass_names = dir(klass)
|
||
|
|
||
|
missing = [x for x in klass_names if x not in nat_names and not x.startswith("_")]
|
||
|
missing.sort()
|
||
|
|
||
|
assert missing == expected
|
||
|
|
||
|
|
||
|
def _get_overlap_public_nat_methods(klass, as_tuple=False):
|
||
|
"""
|
||
|
Get overlapping public methods between NaT and another class.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
klass : type
|
||
|
The class to compare with NaT
|
||
|
as_tuple : bool, default False
|
||
|
Whether to return a list of tuples of the form (klass, method).
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
overlap : list
|
||
|
"""
|
||
|
nat_names = dir(NaT)
|
||
|
klass_names = dir(klass)
|
||
|
|
||
|
overlap = [
|
||
|
x
|
||
|
for x in nat_names
|
||
|
if x in klass_names and not x.startswith("_") and callable(getattr(klass, x))
|
||
|
]
|
||
|
|
||
|
# Timestamp takes precedence over Timedelta in terms of overlap.
|
||
|
if klass is Timedelta:
|
||
|
ts_names = dir(Timestamp)
|
||
|
overlap = [x for x in overlap if x not in ts_names]
|
||
|
|
||
|
if as_tuple:
|
||
|
overlap = [(klass, method) for method in overlap]
|
||
|
|
||
|
overlap.sort()
|
||
|
return overlap
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"klass,expected",
|
||
|
[
|
||
|
(
|
||
|
Timestamp,
|
||
|
[
|
||
|
"as_unit",
|
||
|
"astimezone",
|
||
|
"ceil",
|
||
|
"combine",
|
||
|
"ctime",
|
||
|
"date",
|
||
|
"day_name",
|
||
|
"dst",
|
||
|
"floor",
|
||
|
"fromisocalendar",
|
||
|
"fromisoformat",
|
||
|
"fromordinal",
|
||
|
"fromtimestamp",
|
||
|
"isocalendar",
|
||
|
"isoformat",
|
||
|
"isoweekday",
|
||
|
"month_name",
|
||
|
"now",
|
||
|
"replace",
|
||
|
"round",
|
||
|
"strftime",
|
||
|
"strptime",
|
||
|
"time",
|
||
|
"timestamp",
|
||
|
"timetuple",
|
||
|
"timetz",
|
||
|
"to_datetime64",
|
||
|
"to_numpy",
|
||
|
"to_pydatetime",
|
||
|
"today",
|
||
|
"toordinal",
|
||
|
"tz_convert",
|
||
|
"tz_localize",
|
||
|
"tzname",
|
||
|
"utcfromtimestamp",
|
||
|
"utcnow",
|
||
|
"utcoffset",
|
||
|
"utctimetuple",
|
||
|
"weekday",
|
||
|
],
|
||
|
),
|
||
|
(Timedelta, ["total_seconds"]),
|
||
|
],
|
||
|
)
|
||
|
def test_overlap_public_nat_methods(klass, expected):
|
||
|
# see gh-17327
|
||
|
#
|
||
|
# NaT should have *most* of the Timestamp and Timedelta methods.
|
||
|
# In case when Timestamp, Timedelta, and NaT are overlap, the overlap
|
||
|
# is considered to be with Timestamp and NaT, not Timedelta.
|
||
|
assert _get_overlap_public_nat_methods(klass) == expected
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"compare",
|
||
|
(
|
||
|
_get_overlap_public_nat_methods(Timestamp, True)
|
||
|
+ _get_overlap_public_nat_methods(Timedelta, True)
|
||
|
),
|
||
|
ids=lambda x: f"{x[0].__name__}.{x[1]}",
|
||
|
)
|
||
|
def test_nat_doc_strings(compare):
|
||
|
# see gh-17327
|
||
|
#
|
||
|
# The docstrings for overlapping methods should match.
|
||
|
klass, method = compare
|
||
|
klass_doc = getattr(klass, method).__doc__
|
||
|
|
||
|
if klass == Timestamp and method == "isoformat":
|
||
|
pytest.skip(
|
||
|
"Ignore differences with Timestamp.isoformat() as they're intentional"
|
||
|
)
|
||
|
|
||
|
if method == "to_numpy":
|
||
|
# GH#44460 can return either dt64 or td64 depending on dtype,
|
||
|
# different docstring is intentional
|
||
|
pytest.skip(f"different docstring for {method} is intentional")
|
||
|
|
||
|
nat_doc = getattr(NaT, method).__doc__
|
||
|
assert klass_doc == nat_doc
|
||
|
|
||
|
|
||
|
_ops = {
|
||
|
"left_plus_right": lambda a, b: a + b,
|
||
|
"right_plus_left": lambda a, b: b + a,
|
||
|
"left_minus_right": lambda a, b: a - b,
|
||
|
"right_minus_left": lambda a, b: b - a,
|
||
|
"left_times_right": lambda a, b: a * b,
|
||
|
"right_times_left": lambda a, b: b * a,
|
||
|
"left_div_right": lambda a, b: a / b,
|
||
|
"right_div_left": lambda a, b: b / a,
|
||
|
}
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize("op_name", list(_ops.keys()))
|
||
|
@pytest.mark.parametrize(
|
||
|
"value,val_type",
|
||
|
[
|
||
|
(2, "scalar"),
|
||
|
(1.5, "floating"),
|
||
|
(np.nan, "floating"),
|
||
|
("foo", "str"),
|
||
|
(timedelta(3600), "timedelta"),
|
||
|
(Timedelta("5s"), "timedelta"),
|
||
|
(datetime(2014, 1, 1), "timestamp"),
|
||
|
(Timestamp("2014-01-01"), "timestamp"),
|
||
|
(Timestamp("2014-01-01", tz="UTC"), "timestamp"),
|
||
|
(Timestamp("2014-01-01", tz="US/Eastern"), "timestamp"),
|
||
|
(pytz.timezone("Asia/Tokyo").localize(datetime(2014, 1, 1)), "timestamp"),
|
||
|
],
|
||
|
)
|
||
|
def test_nat_arithmetic_scalar(op_name, value, val_type):
|
||
|
# see gh-6873
|
||
|
invalid_ops = {
|
||
|
"scalar": {"right_div_left"},
|
||
|
"floating": {
|
||
|
"right_div_left",
|
||
|
"left_minus_right",
|
||
|
"right_minus_left",
|
||
|
"left_plus_right",
|
||
|
"right_plus_left",
|
||
|
},
|
||
|
"str": set(_ops.keys()),
|
||
|
"timedelta": {"left_times_right", "right_times_left"},
|
||
|
"timestamp": {
|
||
|
"left_times_right",
|
||
|
"right_times_left",
|
||
|
"left_div_right",
|
||
|
"right_div_left",
|
||
|
},
|
||
|
}
|
||
|
|
||
|
op = _ops[op_name]
|
||
|
|
||
|
if op_name in invalid_ops.get(val_type, set()):
|
||
|
if (
|
||
|
val_type == "timedelta"
|
||
|
and "times" in op_name
|
||
|
and isinstance(value, Timedelta)
|
||
|
):
|
||
|
typs = "(Timedelta|NaTType)"
|
||
|
msg = rf"unsupported operand type\(s\) for \*: '{typs}' and '{typs}'"
|
||
|
elif val_type == "str":
|
||
|
# un-specific check here because the message comes from str
|
||
|
# and varies by method
|
||
|
msg = "|".join(
|
||
|
[
|
||
|
"can only concatenate str",
|
||
|
"unsupported operand type",
|
||
|
"can't multiply sequence",
|
||
|
"Can't convert 'NaTType'",
|
||
|
"must be str, not NaTType",
|
||
|
]
|
||
|
)
|
||
|
else:
|
||
|
msg = "unsupported operand type"
|
||
|
|
||
|
with pytest.raises(TypeError, match=msg):
|
||
|
op(NaT, value)
|
||
|
else:
|
||
|
if val_type == "timedelta" and "div" in op_name:
|
||
|
expected = np.nan
|
||
|
else:
|
||
|
expected = NaT
|
||
|
|
||
|
assert op(NaT, value) is expected
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"val,expected", [(np.nan, NaT), (NaT, np.nan), (np.timedelta64("NaT"), np.nan)]
|
||
|
)
|
||
|
def test_nat_rfloordiv_timedelta(val, expected):
|
||
|
# see gh-#18846
|
||
|
#
|
||
|
# See also test_timedelta.TestTimedeltaArithmetic.test_floordiv
|
||
|
td = Timedelta(hours=3, minutes=4)
|
||
|
assert td // val is expected
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"op_name",
|
||
|
["left_plus_right", "right_plus_left", "left_minus_right", "right_minus_left"],
|
||
|
)
|
||
|
@pytest.mark.parametrize(
|
||
|
"value",
|
||
|
[
|
||
|
DatetimeIndex(["2011-01-01", "2011-01-02"], name="x"),
|
||
|
DatetimeIndex(["2011-01-01", "2011-01-02"], tz="US/Eastern", name="x"),
|
||
|
DatetimeArray._from_sequence(["2011-01-01", "2011-01-02"], dtype="M8[ns]"),
|
||
|
DatetimeArray._from_sequence(
|
||
|
["2011-01-01", "2011-01-02"], dtype=DatetimeTZDtype(tz="US/Pacific")
|
||
|
),
|
||
|
TimedeltaIndex(["1 day", "2 day"], name="x"),
|
||
|
],
|
||
|
)
|
||
|
def test_nat_arithmetic_index(op_name, value):
|
||
|
# see gh-11718
|
||
|
exp_name = "x"
|
||
|
exp_data = [NaT] * 2
|
||
|
|
||
|
if value.dtype.kind == "M" and "plus" in op_name:
|
||
|
expected = DatetimeIndex(exp_data, tz=value.tz, name=exp_name)
|
||
|
else:
|
||
|
expected = TimedeltaIndex(exp_data, name=exp_name)
|
||
|
expected = expected.as_unit(value.unit)
|
||
|
|
||
|
if not isinstance(value, Index):
|
||
|
expected = expected.array
|
||
|
|
||
|
op = _ops[op_name]
|
||
|
result = op(NaT, value)
|
||
|
tm.assert_equal(result, expected)
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"op_name",
|
||
|
["left_plus_right", "right_plus_left", "left_minus_right", "right_minus_left"],
|
||
|
)
|
||
|
@pytest.mark.parametrize("box", [TimedeltaIndex, Series, TimedeltaArray._from_sequence])
|
||
|
def test_nat_arithmetic_td64_vector(op_name, box):
|
||
|
# see gh-19124
|
||
|
vec = box(["1 day", "2 day"], dtype="timedelta64[ns]")
|
||
|
box_nat = box([NaT, NaT], dtype="timedelta64[ns]")
|
||
|
tm.assert_equal(_ops[op_name](vec, NaT), box_nat)
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"dtype,op,out_dtype",
|
||
|
[
|
||
|
("datetime64[ns]", operator.add, "datetime64[ns]"),
|
||
|
("datetime64[ns]", roperator.radd, "datetime64[ns]"),
|
||
|
("datetime64[ns]", operator.sub, "timedelta64[ns]"),
|
||
|
("datetime64[ns]", roperator.rsub, "timedelta64[ns]"),
|
||
|
("timedelta64[ns]", operator.add, "datetime64[ns]"),
|
||
|
("timedelta64[ns]", roperator.radd, "datetime64[ns]"),
|
||
|
("timedelta64[ns]", operator.sub, "datetime64[ns]"),
|
||
|
("timedelta64[ns]", roperator.rsub, "timedelta64[ns]"),
|
||
|
],
|
||
|
)
|
||
|
def test_nat_arithmetic_ndarray(dtype, op, out_dtype):
|
||
|
other = np.arange(10).astype(dtype)
|
||
|
result = op(NaT, other)
|
||
|
|
||
|
expected = np.empty(other.shape, dtype=out_dtype)
|
||
|
expected.fill("NaT")
|
||
|
tm.assert_numpy_array_equal(result, expected)
|
||
|
|
||
|
|
||
|
def test_nat_pinned_docstrings():
|
||
|
# see gh-17327
|
||
|
assert NaT.ctime.__doc__ == Timestamp.ctime.__doc__
|
||
|
|
||
|
|
||
|
def test_to_numpy_alias():
|
||
|
# GH 24653: alias .to_numpy() for scalars
|
||
|
expected = NaT.to_datetime64()
|
||
|
result = NaT.to_numpy()
|
||
|
|
||
|
assert isna(expected) and isna(result)
|
||
|
|
||
|
# GH#44460
|
||
|
result = NaT.to_numpy("M8[s]")
|
||
|
assert isinstance(result, np.datetime64)
|
||
|
assert result.dtype == "M8[s]"
|
||
|
|
||
|
result = NaT.to_numpy("m8[ns]")
|
||
|
assert isinstance(result, np.timedelta64)
|
||
|
assert result.dtype == "m8[ns]"
|
||
|
|
||
|
result = NaT.to_numpy("m8[s]")
|
||
|
assert isinstance(result, np.timedelta64)
|
||
|
assert result.dtype == "m8[s]"
|
||
|
|
||
|
with pytest.raises(ValueError, match="NaT.to_numpy dtype must be a "):
|
||
|
NaT.to_numpy(np.int64)
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"other",
|
||
|
[
|
||
|
Timedelta(0),
|
||
|
Timedelta(0).to_pytimedelta(),
|
||
|
pytest.param(
|
||
|
Timedelta(0).to_timedelta64(),
|
||
|
marks=pytest.mark.xfail(
|
||
|
not np_version_gte1p24p3,
|
||
|
reason="td64 doesn't return NotImplemented, see numpy#17017",
|
||
|
# When this xfail is fixed, test_nat_comparisons_numpy
|
||
|
# can be removed.
|
||
|
),
|
||
|
),
|
||
|
Timestamp(0),
|
||
|
Timestamp(0).to_pydatetime(),
|
||
|
pytest.param(
|
||
|
Timestamp(0).to_datetime64(),
|
||
|
marks=pytest.mark.xfail(
|
||
|
not np_version_gte1p24p3,
|
||
|
reason="dt64 doesn't return NotImplemented, see numpy#17017",
|
||
|
),
|
||
|
),
|
||
|
Timestamp(0).tz_localize("UTC"),
|
||
|
NaT,
|
||
|
],
|
||
|
)
|
||
|
def test_nat_comparisons(compare_operators_no_eq_ne, other):
|
||
|
# GH 26039
|
||
|
opname = compare_operators_no_eq_ne
|
||
|
|
||
|
assert getattr(NaT, opname)(other) is False
|
||
|
|
||
|
op = getattr(operator, opname.strip("_"))
|
||
|
assert op(NaT, other) is False
|
||
|
assert op(other, NaT) is False
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize("other", [np.timedelta64(0, "ns"), np.datetime64("now", "ns")])
|
||
|
def test_nat_comparisons_numpy(other):
|
||
|
# Once numpy#17017 is fixed and the xfailed cases in test_nat_comparisons
|
||
|
# pass, this test can be removed
|
||
|
assert not NaT == other
|
||
|
assert NaT != other
|
||
|
assert not NaT < other
|
||
|
assert not NaT > other
|
||
|
assert not NaT <= other
|
||
|
assert not NaT >= other
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize("other_and_type", [("foo", "str"), (2, "int"), (2.0, "float")])
|
||
|
@pytest.mark.parametrize(
|
||
|
"symbol_and_op",
|
||
|
[("<=", operator.le), ("<", operator.lt), (">=", operator.ge), (">", operator.gt)],
|
||
|
)
|
||
|
def test_nat_comparisons_invalid(other_and_type, symbol_and_op):
|
||
|
# GH#35585
|
||
|
other, other_type = other_and_type
|
||
|
symbol, op = symbol_and_op
|
||
|
|
||
|
assert not NaT == other
|
||
|
assert not other == NaT
|
||
|
|
||
|
assert NaT != other
|
||
|
assert other != NaT
|
||
|
|
||
|
msg = f"'{symbol}' not supported between instances of 'NaTType' and '{other_type}'"
|
||
|
with pytest.raises(TypeError, match=msg):
|
||
|
op(NaT, other)
|
||
|
|
||
|
msg = f"'{symbol}' not supported between instances of '{other_type}' and 'NaTType'"
|
||
|
with pytest.raises(TypeError, match=msg):
|
||
|
op(other, NaT)
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"other",
|
||
|
[
|
||
|
np.array(["foo"] * 2, dtype=object),
|
||
|
np.array([2, 3], dtype="int64"),
|
||
|
np.array([2.0, 3.5], dtype="float64"),
|
||
|
],
|
||
|
ids=["str", "int", "float"],
|
||
|
)
|
||
|
def test_nat_comparisons_invalid_ndarray(other):
|
||
|
# GH#40722
|
||
|
expected = np.array([False, False])
|
||
|
result = NaT == other
|
||
|
tm.assert_numpy_array_equal(result, expected)
|
||
|
result = other == NaT
|
||
|
tm.assert_numpy_array_equal(result, expected)
|
||
|
|
||
|
expected = np.array([True, True])
|
||
|
result = NaT != other
|
||
|
tm.assert_numpy_array_equal(result, expected)
|
||
|
result = other != NaT
|
||
|
tm.assert_numpy_array_equal(result, expected)
|
||
|
|
||
|
for symbol, op in [
|
||
|
("<=", operator.le),
|
||
|
("<", operator.lt),
|
||
|
(">=", operator.ge),
|
||
|
(">", operator.gt),
|
||
|
]:
|
||
|
msg = f"'{symbol}' not supported between"
|
||
|
|
||
|
with pytest.raises(TypeError, match=msg):
|
||
|
op(NaT, other)
|
||
|
|
||
|
if other.dtype == np.dtype("object"):
|
||
|
# uses the reverse operator, so symbol changes
|
||
|
msg = None
|
||
|
with pytest.raises(TypeError, match=msg):
|
||
|
op(other, NaT)
|
||
|
|
||
|
|
||
|
def test_compare_date(fixed_now_ts):
|
||
|
# GH#39151 comparing NaT with date object is deprecated
|
||
|
# See also: tests.scalar.timestamps.test_comparisons::test_compare_date
|
||
|
|
||
|
dt = fixed_now_ts.to_pydatetime().date()
|
||
|
|
||
|
msg = "Cannot compare NaT with datetime.date object"
|
||
|
for left, right in [(NaT, dt), (dt, NaT)]:
|
||
|
assert not left == right
|
||
|
assert left != right
|
||
|
|
||
|
with pytest.raises(TypeError, match=msg):
|
||
|
left < right
|
||
|
with pytest.raises(TypeError, match=msg):
|
||
|
left <= right
|
||
|
with pytest.raises(TypeError, match=msg):
|
||
|
left > right
|
||
|
with pytest.raises(TypeError, match=msg):
|
||
|
left >= right
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"obj",
|
||
|
[
|
||
|
offsets.YearEnd(2),
|
||
|
offsets.YearBegin(2),
|
||
|
offsets.MonthBegin(1),
|
||
|
offsets.MonthEnd(2),
|
||
|
offsets.MonthEnd(12),
|
||
|
offsets.Day(2),
|
||
|
offsets.Day(5),
|
||
|
offsets.Hour(24),
|
||
|
offsets.Hour(3),
|
||
|
offsets.Minute(),
|
||
|
np.timedelta64(3, "h"),
|
||
|
np.timedelta64(4, "h"),
|
||
|
np.timedelta64(3200, "s"),
|
||
|
np.timedelta64(3600, "s"),
|
||
|
np.timedelta64(3600 * 24, "s"),
|
||
|
np.timedelta64(2, "D"),
|
||
|
np.timedelta64(365, "D"),
|
||
|
timedelta(-2),
|
||
|
timedelta(365),
|
||
|
timedelta(minutes=120),
|
||
|
timedelta(days=4, minutes=180),
|
||
|
timedelta(hours=23),
|
||
|
timedelta(hours=23, minutes=30),
|
||
|
timedelta(hours=48),
|
||
|
],
|
||
|
)
|
||
|
def test_nat_addsub_tdlike_scalar(obj):
|
||
|
assert NaT + obj is NaT
|
||
|
assert obj + NaT is NaT
|
||
|
assert NaT - obj is NaT
|
||
|
|
||
|
|
||
|
def test_pickle():
|
||
|
# GH#4606
|
||
|
p = tm.round_trip_pickle(NaT)
|
||
|
assert p is NaT
|