diff --git a/doc/source/whatsnew/v2.1.2.rst b/doc/source/whatsnew/v2.1.2.rst index ed8010c2ea258..d0945c1e530fe 100644 --- a/doc/source/whatsnew/v2.1.2.rst +++ b/doc/source/whatsnew/v2.1.2.rst @@ -34,6 +34,7 @@ Bug fixes - Fixed bug in :meth:`Series.floordiv` for :class:`ArrowDtype` (:issue:`55561`) - Fixed bug in :meth:`Series.rank` for ``string[pyarrow_numpy]`` dtype (:issue:`55362`) - Fixed bug in :meth:`Series.str.extractall` for :class:`ArrowDtype` dtype being converted to object (:issue:`53846`) +- Fixed bug in constructing :class:`Series` when dtype is a timezone aware datetime with non-nanosecond resolution raising ``OutOfBoundsDatetime`` (:issue:`54620`) - Silence ``Period[B]`` warnings introduced by :issue:`53446` during normal plotting activity (:issue:`55138`) .. --------------------------------------------------------------------------- diff --git a/pandas/_libs/tslib.pyi b/pandas/_libs/tslib.pyi index 9819b5173db56..35a5c2626f102 100644 --- a/pandas/_libs/tslib.pyi +++ b/pandas/_libs/tslib.pyi @@ -28,5 +28,5 @@ def array_to_datetime( # returned ndarray may be object dtype or datetime64[ns] def array_to_datetime_with_tz( - values: npt.NDArray[np.object_], tz: tzinfo + values: npt.NDArray[np.object_], tz: tzinfo, unit: str = ... ) -> npt.NDArray[np.int64]: ... diff --git a/pandas/_libs/tslib.pyx b/pandas/_libs/tslib.pyx index 4989feaf84006..93c020a46cbee 100644 --- a/pandas/_libs/tslib.pyx +++ b/pandas/_libs/tslib.pyx @@ -678,7 +678,7 @@ cdef _array_to_datetime_object( return oresult_nd, None -def array_to_datetime_with_tz(ndarray values, tzinfo tz): +def array_to_datetime_with_tz(ndarray values, tzinfo tz, unit="ns"): """ Vectorized analogue to pd.Timestamp(value, tz=tz) @@ -714,7 +714,7 @@ def array_to_datetime_with_tz(ndarray values, tzinfo tz): else: # datetime64, tznaive pydatetime, int, float ts = ts.tz_localize(tz) - ts = ts.as_unit("ns") + ts = ts.as_unit(unit) ival = ts._value # Analogous to: result[i] = ival diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 3cf4dde3015c9..d4fe03fa5ae3f 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -348,7 +348,7 @@ def _from_sequence_not_strict( # DatetimeTZDtype unit = dtype.unit - subarr, tz, inferred_freq = _sequence_to_dt64ns( + subarr, tz, inferred_freq = _sequence_to_dt64( data, copy=copy, tz=tz, @@ -2172,7 +2172,7 @@ def std( # Constructor Helpers -def _sequence_to_dt64ns( +def _sequence_to_dt64( data, *, copy: bool = False, @@ -2198,7 +2198,8 @@ def _sequence_to_dt64ns( Returns ------- result : numpy.ndarray - The sequence converted to a numpy array with dtype ``datetime64[ns]``. + The sequence converted to a numpy array with dtype ``datetime64[unit]``. + Where `unit` is ns unless specified otherwise by `out_unit`. tz : tzinfo or None Either the user-provided tzinfo or one inferred from the data. inferred_freq : Tick or None @@ -2221,9 +2222,9 @@ def _sequence_to_dt64ns( data, copy = maybe_convert_dtype(data, copy, tz=tz) data_dtype = getattr(data, "dtype", None) - out_dtype = DT64NS_DTYPE - if out_unit is not None: - out_dtype = np.dtype(f"M8[{out_unit}]") + if out_unit is None: + out_unit = "ns" + out_dtype = np.dtype(f"M8[{out_unit}]") if data_dtype == object or is_string_dtype(data_dtype): # TODO: We do not have tests specific to string-dtypes, @@ -2234,8 +2235,8 @@ def _sequence_to_dt64ns( elif tz is not None and ambiguous == "raise": # TODO: yearfirst/dayfirst/etc? obj_data = np.asarray(data, dtype=object) - i8data = tslib.array_to_datetime_with_tz(obj_data, tz) - return i8data.view(DT64NS_DTYPE), tz, None + i8data = tslib.array_to_datetime_with_tz(obj_data, tz, out_unit) + return i8data.view(out_dtype), tz, None else: # data comes back here as either i8 to denote UTC timestamps # or M8[ns] to denote wall times diff --git a/pandas/tests/arrays/datetimes/test_constructors.py b/pandas/tests/arrays/datetimes/test_constructors.py index e513457819eb5..8845ab928e252 100644 --- a/pandas/tests/arrays/datetimes/test_constructors.py +++ b/pandas/tests/arrays/datetimes/test_constructors.py @@ -8,7 +8,7 @@ import pandas as pd import pandas._testing as tm from pandas.core.arrays import DatetimeArray -from pandas.core.arrays.datetimes import _sequence_to_dt64ns +from pandas.core.arrays.datetimes import _sequence_to_dt64 class TestDatetimeArrayConstructor: @@ -44,7 +44,7 @@ def test_freq_validation(self): "meth", [ DatetimeArray._from_sequence, - _sequence_to_dt64ns, + _sequence_to_dt64, pd.to_datetime, pd.DatetimeIndex, ], @@ -105,7 +105,7 @@ def test_bool_dtype_raises(self): DatetimeArray._from_sequence(arr) with pytest.raises(TypeError, match=msg): - _sequence_to_dt64ns(arr) + _sequence_to_dt64(arr) with pytest.raises(TypeError, match=msg): pd.DatetimeIndex(arr) @@ -160,8 +160,8 @@ def test_2d(self, order): if order == "F": arr = arr.T - res = _sequence_to_dt64ns(arr) - expected = _sequence_to_dt64ns(arr.ravel()) + res = _sequence_to_dt64(arr) + expected = _sequence_to_dt64(arr.ravel()) tm.assert_numpy_array_equal(res[0].ravel(), expected[0]) assert res[1] == expected[1] diff --git a/pandas/tests/arrays/test_datetimelike.py b/pandas/tests/arrays/test_datetimelike.py index 3f91b9b03e1de..6d756b11df75e 100644 --- a/pandas/tests/arrays/test_datetimelike.py +++ b/pandas/tests/arrays/test_datetimelike.py @@ -27,7 +27,7 @@ PeriodArray, TimedeltaArray, ) -from pandas.core.arrays.datetimes import _sequence_to_dt64ns +from pandas.core.arrays.datetimes import _sequence_to_dt64 from pandas.core.arrays.timedeltas import sequence_to_td64ns @@ -1314,7 +1314,7 @@ def test_from_pandas_array(dtype): expected = cls._from_sequence(data) tm.assert_extension_array_equal(result, expected) - func = {"M8[ns]": _sequence_to_dt64ns, "m8[ns]": sequence_to_td64ns}[dtype] + func = {"M8[ns]": _sequence_to_dt64, "m8[ns]": sequence_to_td64ns}[dtype] result = func(arr)[0] expected = func(data)[0] tm.assert_equal(result, expected) diff --git a/pandas/tests/series/test_constructors.py b/pandas/tests/series/test_constructors.py index 69f6d847f5b19..68fbf079aa472 100644 --- a/pandas/tests/series/test_constructors.py +++ b/pandas/tests/series/test_constructors.py @@ -1148,6 +1148,15 @@ def test_constructor_with_datetime_tz(self): result = DatetimeIndex(s, freq="infer") tm.assert_index_equal(result, dr) + def test_constructor_with_datetime_tz_ms(self): + # GH#54620 explicit frequency + result = Series([Timestamp("2999-01-01")], dtype="datetime64[ms, US/Pacific]") + expected = Series( + np.array(["2999-01-01"], dtype="datetime64[ms]") + ).dt.tz_localize("US/Pacific") + tm.assert_series_equal(result, expected) + assert result.dtype == "datetime64[ms, US/Pacific]" + def test_constructor_with_datetime_tz4(self): # inference s = Series( diff --git a/pandas/tests/test_downstream.py b/pandas/tests/test_downstream.py index 735c6131ba319..72e9b9f991cc9 100644 --- a/pandas/tests/test_downstream.py +++ b/pandas/tests/test_downstream.py @@ -23,7 +23,7 @@ DatetimeArray, TimedeltaArray, ) -from pandas.core.arrays.datetimes import _sequence_to_dt64ns +from pandas.core.arrays.datetimes import _sequence_to_dt64 from pandas.core.arrays.timedeltas import sequence_to_td64ns @@ -316,7 +316,7 @@ def test_from_obscure_array(dtype, array_likes): result = cls._from_sequence(data) tm.assert_extension_array_equal(result, expected) - func = {"M8[ns]": _sequence_to_dt64ns, "m8[ns]": sequence_to_td64ns}[dtype] + func = {"M8[ns]": _sequence_to_dt64, "m8[ns]": sequence_to_td64ns}[dtype] result = func(arr)[0] expected = func(data)[0] tm.assert_equal(result, expected)