diff --git a/doc/source/whatsnew/v1.4.0.rst b/doc/source/whatsnew/v1.4.0.rst index 5b09b62fa9e88..ae00309ba964d 100644 --- a/doc/source/whatsnew/v1.4.0.rst +++ b/doc/source/whatsnew/v1.4.0.rst @@ -150,6 +150,7 @@ Deprecations - Deprecated :meth:`Index.is_type_compatible` (:issue:`42113`) - Deprecated ``method`` argument in :meth:`Index.get_loc`, use ``index.get_indexer([label], method=...)`` instead (:issue:`42269`) - Deprecated treating integer keys in :meth:`Series.__setitem__` as positional when the index is a :class:`Float64Index` not containing the key, a :class:`IntervalIndex` with no entries containing the key, or a :class:`MultiIndex` with leading :class:`Float64Index` level not containing the key (:issue:`33469`) +- Deprecated treating ``numpy.datetime64`` objects as UTC times when passed to the :class:`Timestamp` constructor along with a timezone. In a future version, these will be treated as wall-times. To retain the old behavior, use ``Timestamp(dt64).tz_localize("UTC").tz_convert(tz)`` (:issue:`24559`) - .. --------------------------------------------------------------------------- diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index 88a2706a35aa2..e4e9df5176459 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -1329,6 +1329,19 @@ class Timestamp(_Timestamp): "the tz parameter. Use tz_convert instead.") tzobj = maybe_get_tz(tz) + if tzobj is not None and is_datetime64_object(ts_input): + # GH#24559, GH#42288 In the future we will treat datetime64 as + # wall-time (consistent with DatetimeIndex) + warnings.warn( + "In a future version, when passing a np.datetime64 object and " + "a timezone to Timestamp, the datetime64 will be interpreted " + "as a wall time, not a UTC time. To interpret as a UTC time, " + "use `Timestamp(dt64).tz_localize('UTC').tz_convert(tz)`", + FutureWarning, + stacklevel=1, + ) + # Once this deprecation is enforced, we can do + # return Timestamp(ts_input).tz_localize(tzobj) ts = convert_to_tsobject(ts_input, tzobj, unit, 0, 0, nanosecond or 0) if ts.value == NPY_NAT: diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 0a517b22d1bb3..8513bbb044e83 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -509,6 +509,11 @@ def _check_compatible_with(self, other, setitem: bool = False): # Descriptive Properties def _box_func(self, x) -> Timestamp | NaTType: + if isinstance(x, np.datetime64): + # GH#42228 + # Argument 1 to "signedinteger" has incompatible type "datetime64"; + # expected "Union[SupportsInt, Union[str, bytes], SupportsIndex]" + x = np.int64(x) # type: ignore[arg-type] ts = Timestamp(x, tz=self.tz) # Non-overlapping identity check (left operand type: "Timestamp", # right operand type: "NaTType") diff --git a/pandas/tests/scalar/timestamp/test_constructors.py b/pandas/tests/scalar/timestamp/test_constructors.py index c6c475f5b87a2..b3deb1a57e5c3 100644 --- a/pandas/tests/scalar/timestamp/test_constructors.py +++ b/pandas/tests/scalar/timestamp/test_constructors.py @@ -24,6 +24,24 @@ class TestTimestampConstructors: + def test_constructor_datetime64_with_tz(self): + # GH#42288, GH#24559 + dt = np.datetime64("1970-01-01 05:00:00") + tzstr = "UTC+05:00" + + msg = "interpreted as a wall time" + with tm.assert_produces_warning(FutureWarning, match=msg): + ts = Timestamp(dt, tz=tzstr) + + # Check that we match the old behavior + alt = Timestamp(dt).tz_localize("UTC").tz_convert(tzstr) + assert ts == alt + + # Check that we *don't* match the future behavior + assert ts.hour != 5 + expected_future = Timestamp(dt).tz_localize(tzstr) + assert ts != expected_future + def test_constructor(self): base_str = "2014-07-01 09:00" base_dt = datetime(2014, 7, 1, 9)