diff --git a/pandas/_libs/lib.pyx b/pandas/_libs/lib.pyx index f0da99c795ebf..2ff8c09b9df8b 100644 --- a/pandas/_libs/lib.pyx +++ b/pandas/_libs/lib.pyx @@ -106,7 +106,6 @@ from pandas._libs.tslibs.nattype cimport ( ) from pandas._libs.tslibs.offsets cimport is_offset_object from pandas._libs.tslibs.period cimport is_period_object -from pandas._libs.tslibs.timedeltas cimport convert_to_timedelta64 from pandas._libs.tslibs.timezones cimport tz_compare # constants that will be compared to potentially arbitrarily large @@ -2670,11 +2669,6 @@ def maybe_convert_objects(ndarray[object] objects, elif is_timedelta(val): if convert_non_numeric: seen.timedelta_ = True - try: - convert_to_timedelta64(val, "ns") - except OutOfBoundsTimedelta: - seen.object_ = True - break break else: seen.object_ = True diff --git a/pandas/_libs/tslibs/conversion.pxd b/pandas/_libs/tslibs/conversion.pxd index 6b9f41b1bb06f..de89e7cb204b3 100644 --- a/pandas/_libs/tslibs/conversion.pxd +++ b/pandas/_libs/tslibs/conversion.pxd @@ -45,9 +45,6 @@ cdef int64_t get_datetime64_nanos(object val, NPY_DATETIMEUNIT reso) except? -1 cpdef datetime localize_pydatetime(datetime dt, tzinfo tz) cdef int64_t cast_from_unit(object ts, str unit, NPY_DATETIMEUNIT out_reso=*) except? -1 -cdef (int64_t, int) precision_from_unit( - NPY_DATETIMEUNIT in_reso, NPY_DATETIMEUNIT out_reso=* -) cdef maybe_localize_tso(_TSObject obj, tzinfo tz, NPY_DATETIMEUNIT reso) diff --git a/pandas/_libs/tslibs/timedeltas.pxd b/pandas/_libs/tslibs/timedeltas.pxd index f3473e46b6699..f0d86d2f93788 100644 --- a/pandas/_libs/tslibs/timedeltas.pxd +++ b/pandas/_libs/tslibs/timedeltas.pxd @@ -9,7 +9,6 @@ cpdef int64_t get_unit_for_round(freq, NPY_DATETIMEUNIT creso) except? -1 cpdef int64_t delta_to_nanoseconds( delta, NPY_DATETIMEUNIT reso=*, bint round_ok=* ) except? -1 -cdef convert_to_timedelta64(object ts, str unit) cdef bint is_any_td_scalar(object obj) diff --git a/pandas/_libs/tslibs/timedeltas.pyi b/pandas/_libs/tslibs/timedeltas.pyi index 2200f9ebbbbb5..54a98ba2b4856 100644 --- a/pandas/_libs/tslibs/timedeltas.pyi +++ b/pandas/_libs/tslibs/timedeltas.pyi @@ -70,6 +70,7 @@ def array_to_timedelta64( values: npt.NDArray[np.object_], unit: str | None = ..., errors: str = ..., + creso: int = ..., ) -> np.ndarray: ... # np.ndarray[m8ns] def parse_timedelta_unit(unit: str | None) -> UnitChoices: ... def delta_to_nanoseconds( diff --git a/pandas/_libs/tslibs/timedeltas.pyx b/pandas/_libs/tslibs/timedeltas.pyx index 2d18a275f26f5..53c8a8e73f953 100644 --- a/pandas/_libs/tslibs/timedeltas.pyx +++ b/pandas/_libs/tslibs/timedeltas.pyx @@ -41,7 +41,6 @@ from pandas._libs.missing cimport checknull_with_nat_and_na from pandas._libs.tslibs.base cimport ABCTimestamp from pandas._libs.tslibs.conversion cimport ( cast_from_unit, - precision_from_unit, ) from pandas._libs.tslibs.dtypes cimport ( c_DEPR_UNITS, @@ -289,68 +288,6 @@ cpdef int64_t delta_to_nanoseconds( ) from err -@cython.overflowcheck(True) -cdef object ensure_td64ns(object ts): - """ - Overflow-safe implementation of td64.astype("m8[ns]") - - Parameters - ---------- - ts : np.timedelta64 - - Returns - ------- - np.timedelta64[ns] - """ - cdef: - NPY_DATETIMEUNIT td64_unit - int64_t td64_value, mult - - td64_unit = get_datetime64_unit(ts) - if ( - td64_unit != NPY_DATETIMEUNIT.NPY_FR_ns - and td64_unit != NPY_DATETIMEUNIT.NPY_FR_GENERIC - ): - - td64_value = cnp.get_timedelta64_value(ts) - - mult = precision_from_unit(td64_unit)[0] - try: - # NB: cython#1381 this cannot be *= - td64_value = td64_value * mult - except OverflowError as err: - raise OutOfBoundsTimedelta(ts) from err - - return np.timedelta64(td64_value, "ns") - - return ts - - -cdef convert_to_timedelta64(object ts, str unit): - """ - Convert an incoming object to a timedelta64 if possible. - Before calling, unit must be standardized to avoid repeated unit conversion - - Handle these types of objects: - - timedelta/Timedelta - - Return an timedelta64[ns] object - """ - # Caller is responsible for checking unit not in ["Y", "y", "M"] - if isinstance(ts, _Timedelta): - # already in the proper format - if ts._creso != NPY_FR_ns: - ts = ts.as_unit("ns").asm8 - else: - ts = np.timedelta64(ts._value, "ns") - - elif PyDelta_Check(ts): - ts = np.timedelta64(delta_to_nanoseconds(ts), "ns") - elif not cnp.is_timedelta64_object(ts): - raise TypeError(f"Invalid type for timedelta scalar: {type(ts)}") - return ts.astype("timedelta64[ns]") - - cdef _numeric_to_td64ns(object item, str unit): # caller is responsible for checking # assert unit not in ["Y", "y", "M"] @@ -369,10 +306,34 @@ cdef _numeric_to_td64ns(object item, str unit): return ts +# TODO: de-duplicate with DatetimeParseState +cdef class ResoState: + cdef: + NPY_DATETIMEUNIT creso + bint creso_ever_changed + + def __cinit__(self, NPY_DATETIMEUNIT creso): + self.creso = creso + self.creso_ever_changed = False + + cdef bint update_creso(self, NPY_DATETIMEUNIT item_reso) noexcept: + # Return a bool indicating whether we bumped to a higher resolution + if self.creso == NPY_DATETIMEUNIT.NPY_FR_GENERIC: + self.creso = item_reso + elif item_reso > self.creso: + self.creso = item_reso + self.creso_ever_changed = True + return True + return False + + @cython.boundscheck(False) @cython.wraparound(False) def array_to_timedelta64( - ndarray values, str unit=None, str errors="raise" + ndarray values, + str unit=None, + str errors="raise", + NPY_DATETIMEUNIT creso=NPY_DATETIMEUNIT.NPY_FR_GENERIC, ) -> ndarray: # values is object-dtype, may be 2D """ @@ -394,6 +355,10 @@ def array_to_timedelta64( cnp.broadcast mi = cnp.PyArray_MultiIterNew2(result, values) cnp.flatiter it str parsed_unit = parse_timedelta_unit(unit or "ns") + NPY_DATETIMEUNIT item_reso + ResoState state = ResoState(creso) + bint infer_reso = creso == NPY_DATETIMEUNIT.NPY_FR_GENERIC + ndarray iresult = result.view("i8") if values.descr.type_num != cnp.NPY_OBJECT: # raise here otherwise we segfault below @@ -421,18 +386,58 @@ def array_to_timedelta64( ival = NPY_NAT elif cnp.is_timedelta64_object(item): - td64ns_obj = ensure_td64ns(item) - ival = cnp.get_timedelta64_value(td64ns_obj) + # TODO: de-duplicate this with Timedelta.__new__ + ival = cnp.get_timedelta64_value(item) + dt64_reso = get_datetime64_unit(item) + if not ( + is_supported_unit(dt64_reso) or + dt64_reso in [ + NPY_DATETIMEUNIT.NPY_FR_m, + NPY_DATETIMEUNIT.NPY_FR_h, + NPY_DATETIMEUNIT.NPY_FR_D, + NPY_DATETIMEUNIT.NPY_FR_W, + NPY_DATETIMEUNIT.NPY_FR_GENERIC + ] + ): + err = npy_unit_to_abbrev(dt64_reso) + raise ValueError( + f"Unit {err} is not supported. " + "Only unambiguous timedelta values durations are supported. " + "Allowed units are 'W', 'D', 'h', 'm', 's', 'ms', 'us', 'ns'") + + item_reso = get_supported_reso(dt64_reso) + state.update_creso(item_reso) + if infer_reso: + creso = state.creso + if dt64_reso != NPY_DATETIMEUNIT.NPY_FR_GENERIC: + try: + ival = convert_reso( + ival, + dt64_reso, + creso, + round_ok=True, + ) + except (OverflowError, OutOfBoundsDatetime) as err: + raise OutOfBoundsTimedelta(item) from err + else: + # e.g. NaT + pass elif isinstance(item, _Timedelta): - if item._creso != NPY_FR_ns: - ival = item.as_unit("ns")._value - else: - ival = item._value + item_reso = item._creso + state.update_creso(item_reso) + if infer_reso: + creso = state.creso + + ival = (<_Timedelta>item)._as_creso(creso)._value elif PyDelta_Check(item): # i.e. isinstance(item, timedelta) - ival = delta_to_nanoseconds(item) + item_reso = NPY_DATETIMEUNIT.NPY_FR_us + state.update_creso(item_reso) + if infer_reso: + creso = state.creso + ival = delta_to_nanoseconds(item, reso=creso) elif isinstance(item, str): if ( @@ -443,13 +448,27 @@ def array_to_timedelta64( else: ival = parse_timedelta_string(item) + item_reso = NPY_FR_ns + state.update_creso(item_reso) + if infer_reso: + creso = state.creso + elif is_tick_object(item): - ival = item.nanos + item_reso = get_supported_reso(item._creso) + state.update_creso(item_reso) + if infer_reso: + creso = state.creso + ival = delta_to_nanoseconds(item, reso=creso) elif is_integer_object(item) or is_float_object(item): td64ns_obj = _numeric_to_td64ns(item, parsed_unit) ival = cnp.get_timedelta64_value(td64ns_obj) + item_reso = NPY_FR_ns + state.update_creso(item_reso) + if infer_reso: + creso = state.creso + else: raise TypeError(f"Invalid type for timedelta scalar: {type(item)}") @@ -467,7 +486,29 @@ def array_to_timedelta64( cnp.PyArray_MultiIter_NEXT(mi) - return result + if infer_reso: + if state.creso_ever_changed: + # We encountered mismatched resolutions, need to re-parse with + # the correct one. + return array_to_timedelta64( + values, + unit=unit, + errors=errors, + creso=state.creso, + ) + elif state.creso == NPY_DATETIMEUNIT.NPY_FR_GENERIC: + # i.e. we never encountered anything non-NaT, default to "s". This + # ensures that insert and concat-like operations with NaT + # do not upcast units + result = iresult.view("m8[s]") + else: + # Otherwise we can use the single reso that we encountered and avoid + # a second pass. + abbrev = npy_unit_to_abbrev(state.creso) + result = iresult.view(f"m8[{abbrev}]") + + abbrev = npy_unit_to_abbrev(creso) + return result.view(f"m8[{abbrev}]") @cython.cpow(True) diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index 1647a7f6714ed..0de882ee82d34 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -1251,7 +1251,7 @@ def _objects_to_td64ns( values = np.asarray(data, dtype=np.object_) result = array_to_timedelta64(values, unit=unit, errors=errors) - return result.view("timedelta64[ns]") + return result def _validate_td64_dtype(dtype) -> DtypeObj: diff --git a/pandas/tests/arithmetic/test_timedelta64.py b/pandas/tests/arithmetic/test_timedelta64.py index 9a54386abf281..d5063f58e5516 100644 --- a/pandas/tests/arithmetic/test_timedelta64.py +++ b/pandas/tests/arithmetic/test_timedelta64.py @@ -717,7 +717,7 @@ def test_tdi_add_overflow(self): ) # These should not overflow! - exp = TimedeltaIndex([NaT]) + exp = TimedeltaIndex([NaT], dtype="m8[ns]") result = pd.to_timedelta([NaT]) - Timedelta("1 days") tm.assert_index_equal(result, exp) @@ -2216,7 +2216,7 @@ def test_float_series_rdiv_td64arr(self, box_with_array, names): def test_td64arr_all_nat_div_object_dtype_numeric(self, box_with_array): # GH#39750 make sure we infer the result as td64 - tdi = TimedeltaIndex([NaT, NaT]) + tdi = TimedeltaIndex([NaT, NaT], dtype="m8[ns]") left = tm.box_expected(tdi, box_with_array) right = np.array([2, 2.0], dtype=object) diff --git a/pandas/tests/dtypes/test_inference.py b/pandas/tests/dtypes/test_inference.py index 1efa60d1fbc4c..f7e9c276d72ec 100644 --- a/pandas/tests/dtypes/test_inference.py +++ b/pandas/tests/dtypes/test_inference.py @@ -808,7 +808,7 @@ def test_maybe_convert_objects_datetime(self): tm.assert_numpy_array_equal(out, exp) arr = np.array([pd.NaT, np.timedelta64(1, "s")], dtype=object) - exp = np.array([np.timedelta64("NaT"), np.timedelta64(1, "s")], dtype="m8[ns]") + exp = np.array([np.timedelta64("NaT"), np.timedelta64(1, "s")], dtype="m8[s]") out = lib.maybe_convert_objects(arr, convert_non_numeric=True) tm.assert_numpy_array_equal(out, exp) @@ -863,7 +863,7 @@ def test_maybe_convert_objects_datetime_overflow_safe(self, dtype): if dtype == "datetime64[ns]": expected = np.array(["2363-10-04"], dtype="M8[us]") else: - expected = arr + expected = arr.astype("m8[us]") tm.assert_numpy_array_equal(out, expected) def test_maybe_convert_objects_mixed_datetimes(self): diff --git a/pandas/tests/extension/test_arrow.py b/pandas/tests/extension/test_arrow.py index c1e01bbbe57a0..ae267024a44b9 100644 --- a/pandas/tests/extension/test_arrow.py +++ b/pandas/tests/extension/test_arrow.py @@ -591,14 +591,6 @@ def test_reduce_frame(self, data, all_numeric_reductions, skipna, request): if data.dtype._is_numeric: mark = pytest.mark.xfail(reason="skew not implemented") request.applymarker(mark) - elif ( - op_name in ["std", "sem"] - and pa.types.is_date64(data._pa_array.type) - and skipna - ): - # overflow - mark = pytest.mark.xfail(reason="Cannot cast") - request.applymarker(mark) return super().test_reduce_frame(data, all_numeric_reductions, skipna) @pytest.mark.parametrize("typ", ["int64", "uint64", "float64"]) diff --git a/pandas/tests/frame/methods/test_dtypes.py b/pandas/tests/frame/methods/test_dtypes.py index bf01ec73cf72b..19c05f3c43a78 100644 --- a/pandas/tests/frame/methods/test_dtypes.py +++ b/pandas/tests/frame/methods/test_dtypes.py @@ -103,7 +103,7 @@ def test_dtypes_timedeltas(self): ) result = df.dtypes expected = Series( - [np.dtype("datetime64[ns]"), np.dtype("timedelta64[ns]")], index=list("AB") + [np.dtype("datetime64[ns]"), np.dtype("timedelta64[us]")], index=list("AB") ) tm.assert_series_equal(result, expected) @@ -112,7 +112,7 @@ def test_dtypes_timedeltas(self): expected = Series( [ np.dtype("datetime64[ns]"), - np.dtype("timedelta64[ns]"), + np.dtype("timedelta64[us]"), np.dtype("datetime64[ns]"), ], index=list("ABC"), @@ -125,7 +125,7 @@ def test_dtypes_timedeltas(self): expected = Series( [ np.dtype("datetime64[ns]"), - np.dtype("timedelta64[ns]"), + np.dtype("timedelta64[us]"), np.dtype("datetime64[ns]"), np.dtype("int64"), ], diff --git a/pandas/tests/frame/test_constructors.py b/pandas/tests/frame/test_constructors.py index 264011edb65b5..544a47526b86f 100644 --- a/pandas/tests/frame/test_constructors.py +++ b/pandas/tests/frame/test_constructors.py @@ -888,15 +888,15 @@ def create_data(constructor): tm.assert_frame_equal(result_Timestamp, expected) @pytest.mark.parametrize( - "klass,name", + "klass,exp_dtype", [ - (lambda x: np.timedelta64(x, "D"), "timedelta64"), - (lambda x: timedelta(days=x), "pytimedelta"), - (lambda x: Timedelta(x, "D"), "Timedelta[ns]"), - (lambda x: Timedelta(x, "D").as_unit("s"), "Timedelta[s]"), + (lambda x: np.timedelta64(x, "D"), "m8[s]"), + (lambda x: timedelta(days=x), "m8[us]"), + (lambda x: Timedelta(x, "D"), "m8[ns]"), + (lambda x: Timedelta(x, "D").as_unit("s"), "m8[s]"), ], ) - def test_constructor_dict_timedelta64_index(self, klass, name): + def test_constructor_dict_timedelta64_index(self, klass, exp_dtype): # GH 10160 td_as_int = [1, 2, 3, 4] @@ -911,6 +911,7 @@ def test_constructor_dict_timedelta64_index(self, klass, name): ], index=[Timedelta(td, "D") for td in td_as_int], ) + expected.index = expected.index.astype(exp_dtype) result = DataFrame(data) @@ -3257,17 +3258,8 @@ def test_out_of_s_bounds_datetime64(self, constructor): @pytest.mark.parametrize("cls", [timedelta, np.timedelta64]) def test_from_out_of_bounds_ns_timedelta( - self, constructor, cls, request, box, frame_or_series + self, constructor, cls, box, frame_or_series ): - # scalar that won't fit in nanosecond td64, but will fit in microsecond - if box is list or (frame_or_series is Series and box is dict): - mark = pytest.mark.xfail( - reason="TimedeltaArray constructor has been updated to cast td64 " - "to non-nano, but TimedeltaArray._from_sequence has not", - strict=True, - ) - request.applymarker(mark) - scalar = datetime(9999, 1, 1) - datetime(1970, 1, 1) exp_dtype = "m8[us]" # smallest reso that fits if cls is np.timedelta64: diff --git a/pandas/tests/frame/test_reductions.py b/pandas/tests/frame/test_reductions.py index 4d235587c2407..98a42f58aff40 100644 --- a/pandas/tests/frame/test_reductions.py +++ b/pandas/tests/frame/test_reductions.py @@ -613,7 +613,7 @@ def test_sem(self, datetime_frame): "D": Series([np.nan], dtype="str"), "E": Categorical([np.nan], categories=["a"]), "F": DatetimeIndex([pd.NaT], dtype="M8[ns]"), - "G": to_timedelta([pd.NaT]), + "G": to_timedelta([pd.NaT]).as_unit("ns"), }, ), ( diff --git a/pandas/tests/indexes/interval/test_interval.py b/pandas/tests/indexes/interval/test_interval.py index b302e865eebd1..cf2cc55446bb2 100644 --- a/pandas/tests/indexes/interval/test_interval.py +++ b/pandas/tests/indexes/interval/test_interval.py @@ -82,10 +82,7 @@ def test_properties(self, closed): [1, 1, 2, 5, 15, 53, 217, 1014, 5335, 31240, 201608], [-np.inf, -100, -10, 0.5, 1, 1.5, 3.8, 101, 202, np.inf], date_range("2017-01-01", "2017-01-04"), - pytest.param( - date_range("2017-01-01", "2017-01-04", unit="s"), - marks=pytest.mark.xfail(reason="mismatched result unit"), - ), + date_range("2017-01-01", "2017-01-04", unit="s"), pd.to_timedelta(["1ns", "2ms", "3s", "4min", "5h", "6D"]), ], ) diff --git a/pandas/tests/indexes/period/test_indexing.py b/pandas/tests/indexes/period/test_indexing.py index 75382cb735288..77c75e87b89ed 100644 --- a/pandas/tests/indexes/period/test_indexing.py +++ b/pandas/tests/indexes/period/test_indexing.py @@ -523,7 +523,7 @@ def test_get_indexer2(self): tol_bad = [ Timedelta("2 hour").to_timedelta64(), Timedelta("1 hour").to_timedelta64(), - np.timedelta64(1, "M"), + np.timedelta64(2629746, "s"), ] with pytest.raises( libperiod.IncompatibleFrequency, match="Input has different freq=None from" diff --git a/pandas/tests/indexes/test_index_new.py b/pandas/tests/indexes/test_index_new.py index dd228e6b713b5..3687fe921924f 100644 --- a/pandas/tests/indexes/test_index_new.py +++ b/pandas/tests/indexes/test_index_new.py @@ -141,6 +141,9 @@ def test_constructor_infer_nat_dt_like( if dtype[0] == "d": # we infer all-NaT as second resolution expected = expected.astype("M8[ns]") + if dtype[0] == "t": + # we infer all-NaT as second resolution + expected = expected.astype("m8[ns]") assert expected.dtype == dtype data = [ctor] data.insert(pos, nulls_fixture) diff --git a/pandas/tests/io/json/test_pandas.py b/pandas/tests/io/json/test_pandas.py index 9c93be0937e91..aa37bd9ad3db7 100644 --- a/pandas/tests/io/json/test_pandas.py +++ b/pandas/tests/io/json/test_pandas.py @@ -1142,7 +1142,9 @@ def test_timedelta(self): result = read_json(StringIO(ser.to_json()), typ="series").apply(converter) tm.assert_series_equal(result, ser) - ser = Series([timedelta(23), timedelta(seconds=5)], index=Index([0, 1])) + ser = Series( + [timedelta(23), timedelta(seconds=5)], index=Index([0, 1]), dtype="m8[ns]" + ) assert ser.dtype == "timedelta64[ns]" with tm.assert_produces_warning(Pandas4Warning, match=msg): result = read_json(StringIO(ser.to_json()), typ="series").apply(converter) @@ -1163,6 +1165,7 @@ def test_timedelta2(self): "c": date_range(start="20130101", periods=2), } ) + frame["a"] = frame["a"].astype("m8[ns]") msg = ( "The default 'epoch' date format is deprecated and will be removed " "in a future version, please use 'iso' date format instead." @@ -1196,6 +1199,9 @@ def test_timedelta_to_json(self, as_object, date_format, timedelta_typ): data.append("a") ser = Series(data, index=data) + if not as_object: + ser = ser.astype("m8[ns]") + ser.index = ser.index.astype("m8[ns]") expected_warning = None if date_format == "iso": expected = ( @@ -1220,7 +1226,8 @@ def test_timedelta_to_json(self, as_object, date_format, timedelta_typ): @pytest.mark.parametrize("timedelta_typ", [pd.Timedelta, timedelta]) def test_timedelta_to_json_fractional_precision(self, as_object, timedelta_typ): data = [timedelta_typ(milliseconds=42)] - ser = Series(data, index=data) + ser = Series(data, index=data).astype("m8[ns]") + ser.index = ser.index.astype("m8[ns]") warn = Pandas4Warning if as_object: ser = ser.astype(object) diff --git a/pandas/tests/series/indexing/test_indexing.py b/pandas/tests/series/indexing/test_indexing.py index f2a604dcc0787..32ca3f1ba11c3 100644 --- a/pandas/tests/series/indexing/test_indexing.py +++ b/pandas/tests/series/indexing/test_indexing.py @@ -253,17 +253,17 @@ def test_timedelta_assignment(): # GH 8209 s = Series([], dtype=object) s.loc["B"] = timedelta(1) - expected = Series(Timedelta("1 days"), dtype="timedelta64[ns]", index=["B"]) + expected = Series(Timedelta("1 days"), dtype="timedelta64[us]", index=["B"]) tm.assert_series_equal(s, expected) s = s.reindex(s.index.insert(0, "A")) expected = Series( - [np.nan, Timedelta("1 days")], dtype="timedelta64[ns]", index=["A", "B"] + [np.nan, Timedelta("1 days")], dtype="timedelta64[us]", index=["A", "B"] ) tm.assert_series_equal(s, expected) s.loc["A"] = timedelta(1) - expected = Series(Timedelta("1 days"), dtype="timedelta64[ns]", index=["A", "B"]) + expected = Series(Timedelta("1 days"), dtype="timedelta64[us]", index=["A", "B"]) tm.assert_series_equal(s, expected) diff --git a/pandas/tests/series/test_constructors.py b/pandas/tests/series/test_constructors.py index 3c3a06583883f..ff5d81903fcd5 100644 --- a/pandas/tests/series/test_constructors.py +++ b/pandas/tests/series/test_constructors.py @@ -1504,14 +1504,14 @@ def test_fromValue(self, datetime_series, using_infer_string): def test_constructor_dtype_timedelta64(self): # basic td = Series([timedelta(days=i) for i in range(3)]) - assert td.dtype == "timedelta64[ns]" + assert td.dtype == "timedelta64[us]" td = Series([timedelta(days=1)]) - assert td.dtype == "timedelta64[ns]" + assert td.dtype == "timedelta64[us]" td = Series([timedelta(days=1), timedelta(days=2), np.timedelta64(1, "s")]) - assert td.dtype == "timedelta64[ns]" + assert td.dtype == "timedelta64[us]" # mixed with NaT td = Series([timedelta(days=1), NaT], dtype="m8[ns]") @@ -1539,13 +1539,13 @@ def test_constructor_dtype_timedelta64(self): assert td.dtype == "timedelta64[ns]" td = Series([np.timedelta64(1, "s")]) - assert td.dtype == "timedelta64[ns]" + assert td.dtype == "timedelta64[s]" # valid astype td.astype("int64") # invalid casting - msg = r"Converting from timedelta64\[ns\] to int32 is not supported" + msg = r"Converting from timedelta64\[s\] to int32 is not supported" with pytest.raises(TypeError, match=msg): td.astype("int32") diff --git a/pandas/tests/tools/test_to_timedelta.py b/pandas/tests/tools/test_to_timedelta.py index 9d5866ef97017..ad9d11c980f05 100644 --- a/pandas/tests/tools/test_to_timedelta.py +++ b/pandas/tests/tools/test_to_timedelta.py @@ -27,6 +27,19 @@ class TestTimedeltas: + def test_to_timedelta_month_raises(self): + obj = np.timedelta64(1, "M") + + msg = "Unit M is not supported." + with pytest.raises(ValueError, match=msg): + to_timedelta(obj) + with pytest.raises(ValueError, match=msg): + pd.Timedelta(obj) + with pytest.raises(ValueError, match=msg): + to_timedelta([obj]) + with pytest.raises(ValueError, match=msg): + TimedeltaIndex([obj]) + def test_to_timedelta_none(self): # GH#23055 assert to_timedelta(None) is pd.NaT