diff --git a/doc/source/whatsnew/v1.4.0.rst b/doc/source/whatsnew/v1.4.0.rst index d770782d5dc62..cfb9cef79cd46 100644 --- a/doc/source/whatsnew/v1.4.0.rst +++ b/doc/source/whatsnew/v1.4.0.rst @@ -640,6 +640,7 @@ Other Deprecations - Deprecated passing non boolean argument to sort in :func:`concat` (:issue:`41518`) - Deprecated passing arguments as positional for :func:`read_fwf` other than ``filepath_or_buffer`` (:issue:`41485`): - Deprecated passing ``skipna=None`` for :meth:`DataFrame.mad` and :meth:`Series.mad`, pass ``skipna=True`` instead (:issue:`44580`) +- Deprecated the behavior of :func:`to_datetime` with the string "now" with ``utc=False``; in a future version this will match ``Timestamp("now")``, which in turn matches :meth:`Timestamp.now` returning the local time (:issue:`18705`) - Deprecated :meth:`DateOffset.apply`, use ``offset + other`` instead (:issue:`44522`) - Deprecated parameter ``names`` in :meth:`Index.copy` (:issue:`44916`) - A deprecation warning is now shown for :meth:`DataFrame.to_latex` indicating the arguments signature may change and emulate more the arguments to :meth:`.Styler.to_latex` in future versions (:issue:`44411`) diff --git a/pandas/_libs/tslib.pyx b/pandas/_libs/tslib.pyx index 6feb9ec768655..2883c910b3833 100644 --- a/pandas/_libs/tslib.pyx +++ b/pandas/_libs/tslib.pyx @@ -1,3 +1,5 @@ +import warnings + import cython from cpython.datetime cimport ( @@ -516,7 +518,7 @@ cpdef array_to_datetime( if string_to_dts_failed: # An error at this point is a _parsing_ error # specifically _not_ OutOfBoundsDatetime - if _parse_today_now(val, &iresult[i]): + if _parse_today_now(val, &iresult[i], utc): continue elif require_iso8601: # if requiring iso8601 strings, skip trying @@ -755,14 +757,23 @@ cdef _array_to_datetime_object( return oresult, None -cdef inline bint _parse_today_now(str val, int64_t* iresult): +cdef inline bint _parse_today_now(str val, int64_t* iresult, bint utc): # We delay this check for as long as possible # because it catches relatively rare cases - if val == 'now': - # Note: this is *not* the same as Timestamp('now') + if val == "now": iresult[0] = Timestamp.utcnow().value + if not utc: + # GH#18705 make sure to_datetime("now") matches Timestamp("now") + warnings.warn( + "The parsing of 'now' in pd.to_datetime without `utc=True` is " + "deprecated. In a future version, this will match Timestamp('now') " + "and Timestamp.now()", + FutureWarning, + stacklevel=1, + ) + return True - elif val == 'today': + elif val == "today": iresult[0] = Timestamp.today().value return True return False diff --git a/pandas/tests/arithmetic/test_datetime64.py b/pandas/tests/arithmetic/test_datetime64.py index 4414462dd9a48..212cfc267cb00 100644 --- a/pandas/tests/arithmetic/test_datetime64.py +++ b/pandas/tests/arithmetic/test_datetime64.py @@ -1679,8 +1679,8 @@ def test_dt64_series_arith_overflow(self): tm.assert_series_equal(res, -expected) def test_datetimeindex_sub_timestamp_overflow(self): - dtimax = pd.to_datetime(["now", Timestamp.max]) - dtimin = pd.to_datetime(["now", Timestamp.min]) + dtimax = pd.to_datetime(["2021-12-28 17:19", Timestamp.max]) + dtimin = pd.to_datetime(["2021-12-28 17:19", Timestamp.min]) tsneg = Timestamp("1950-01-01") ts_neg_variants = [ @@ -1718,8 +1718,8 @@ def test_datetimeindex_sub_timestamp_overflow(self): def test_datetimeindex_sub_datetimeindex_overflow(self): # GH#22492, GH#22508 - dtimax = pd.to_datetime(["now", Timestamp.max]) - dtimin = pd.to_datetime(["now", Timestamp.min]) + dtimax = pd.to_datetime(["2021-12-28 17:19", Timestamp.max]) + dtimin = pd.to_datetime(["2021-12-28 17:19", Timestamp.min]) ts_neg = pd.to_datetime(["1950-01-01", "1950-01-01"]) ts_pos = pd.to_datetime(["1980-01-01", "1980-01-01"]) diff --git a/pandas/tests/tools/test_to_datetime.py b/pandas/tests/tools/test_to_datetime.py index 50cb16c72953e..5d5e01084345d 100644 --- a/pandas/tests/tools/test_to_datetime.py +++ b/pandas/tests/tools/test_to_datetime.py @@ -591,9 +591,15 @@ def test_to_datetime_unparseable_ignore(self): def test_to_datetime_now(self): # See GH#18666 with tm.set_timezone("US/Eastern"): - npnow = np.datetime64("now").astype("datetime64[ns]") - pdnow = to_datetime("now") - pdnow2 = to_datetime(["now"])[0] + msg = "The parsing of 'now' in pd.to_datetime" + with tm.assert_produces_warning( + FutureWarning, match=msg, check_stacklevel=False + ): + # checking stacklevel is tricky because we go through cython code + # GH#18705 + npnow = np.datetime64("now").astype("datetime64[ns]") + pdnow = to_datetime("now") + pdnow2 = to_datetime(["now"])[0] # These should all be equal with infinite perf; this gives # a generous margin of 10 seconds @@ -632,7 +638,12 @@ def test_to_datetime_today(self, tz): @pytest.mark.parametrize("arg", ["now", "today"]) def test_to_datetime_today_now_unicode_bytes(self, arg): - to_datetime([arg]) + warn = FutureWarning if arg == "now" else None + msg = "The parsing of 'now' in pd.to_datetime" + with tm.assert_produces_warning(warn, match=msg, check_stacklevel=False): + # checking stacklevel is tricky because we go through cython code + # GH#18705 + to_datetime([arg]) @pytest.mark.parametrize( "dt", [np.datetime64("2000-01-01"), np.datetime64("2000-01-02")]