diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 93484c0c96d7b..d6a547b0cd98a 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -921,6 +921,7 @@ Conversion - Bug in :meth:`Series.astype` might modify read-only array inplace when casting to a string dtype (:issue:`57212`) - Bug in :meth:`Series.convert_dtypes` and :meth:`DataFrame.convert_dtypes` removing timezone information for objects with :class:`ArrowDtype` (:issue:`60237`) - Bug in :meth:`Series.reindex` not maintaining ``float32`` type when a ``reindex`` introduces a missing value (:issue:`45857`) +- Bug in :meth:`to_datetime` and :meth:`to_timedelta` with input ``None`` returning ``None`` instead of ``NaT``, inconsistent with other conversion methods (:issue:`23055`) Strings ^^^^^^^ diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 427e9594c3a24..93a7de467dd97 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -10519,8 +10519,12 @@ def truncate( if ax._is_all_dates: from pandas.core.tools.datetimes import to_datetime - before = to_datetime(before) - after = to_datetime(after) + if before is not None: + # Avoid converting to NaT + before = to_datetime(before) + if after is not None: + # Avoid converting to NaT + after = to_datetime(after) if before is not None and after is not None and before > after: raise ValueError(f"Truncate: {after} must be after {before}") diff --git a/pandas/core/tools/datetimes.py b/pandas/core/tools/datetimes.py index 040fcd02ab211..3156abc1e4bf6 100644 --- a/pandas/core/tools/datetimes.py +++ b/pandas/core/tools/datetimes.py @@ -21,6 +21,7 @@ tslib, ) from pandas._libs.tslibs import ( + NaT, OutOfBoundsDatetime, Timedelta, Timestamp, @@ -676,7 +677,7 @@ def to_datetime( unit: str | None = None, origin: str = "unix", cache: bool = True, -) -> DatetimeIndex | Series | DatetimeScalar | NaTType | None: +) -> DatetimeIndex | Series | DatetimeScalar | NaTType: """ Convert argument to datetime. @@ -989,7 +990,7 @@ def to_datetime( if exact is not lib.no_default and format in {"mixed", "ISO8601"}: raise ValueError("Cannot use 'exact' when 'format' is 'mixed' or 'ISO8601'") if arg is None: - return None + return NaT if origin != "unix": arg = _adjust_to_origin(arg, origin, unit) diff --git a/pandas/core/tools/timedeltas.py b/pandas/core/tools/timedeltas.py index 8d82a5c213910..dcadb9c24c213 100644 --- a/pandas/core/tools/timedeltas.py +++ b/pandas/core/tools/timedeltas.py @@ -182,7 +182,7 @@ def to_timedelta( raise ValueError("errors must be one of 'raise', or 'coerce'.") if arg is None: - return arg + return NaT elif isinstance(arg, ABCSeries): values = _convert_listlike(arg._values, unit=unit, errors=errors) return arg._constructor(values, index=arg.index, name=arg.name) diff --git a/pandas/tests/tools/test_to_datetime.py b/pandas/tests/tools/test_to_datetime.py index bf85199ec4f9f..8f242163f9f0c 100644 --- a/pandas/tests/tools/test_to_datetime.py +++ b/pandas/tests/tools/test_to_datetime.py @@ -531,6 +531,10 @@ def test_to_datetime_parse_timezone_keeps_name(self): class TestToDatetime: + def test_to_datetime_none(self): + # GH#23055 + assert to_datetime(None) is NaT + @pytest.mark.filterwarnings("ignore:Could not infer format") def test_to_datetime_overflow(self): # we should get an OutOfBoundsDatetime, NOT OverflowError diff --git a/pandas/tests/tools/test_to_timedelta.py b/pandas/tests/tools/test_to_timedelta.py index bfbefc50e65ba..08ad7b7fb1b93 100644 --- a/pandas/tests/tools/test_to_timedelta.py +++ b/pandas/tests/tools/test_to_timedelta.py @@ -27,6 +27,10 @@ class TestTimedeltas: + def test_to_timedelta_none(self): + # GH#23055 + assert to_timedelta(None) is pd.NaT + def test_to_timedelta_dt64_raises(self): # Passing datetime64-dtype data to TimedeltaIndex is no longer # supported GH#29794