1from __future__ import annotations
2
3from datetime import timedelta
4import operator
5from typing import (
6 TYPE_CHECKING,
7 cast,
8)
9
10import numpy as np
11
12from pandas._libs import (
13 lib,
14 tslibs,
15)
16from pandas._libs.tslibs import (
17 NaT,
18 NaTType,
19 Tick,
20 Timedelta,
21 astype_overflowsafe,
22 get_supported_dtype,
23 iNaT,
24 is_supported_dtype,
25 periods_per_second,
26)
27from pandas._libs.tslibs.conversion import cast_from_unit_vectorized
28from pandas._libs.tslibs.fields import (
29 get_timedelta_days,
30 get_timedelta_field,
31)
32from pandas._libs.tslibs.timedeltas import (
33 array_to_timedelta64,
34 floordiv_object_array,
35 ints_to_pytimedelta,
36 parse_timedelta_unit,
37 truediv_object_array,
38)
39from pandas.compat.numpy import function as nv
40from pandas.util._validators import validate_endpoints
41
42from pandas.core.dtypes.common import (
43 TD64NS_DTYPE,
44 is_float_dtype,
45 is_integer_dtype,
46 is_object_dtype,
47 is_scalar,
48 is_string_dtype,
49 pandas_dtype,
50)
51from pandas.core.dtypes.dtypes import ExtensionDtype
52from pandas.core.dtypes.missing import isna
53
54from pandas.core import (
55 nanops,
56 roperator,
57)
58from pandas.core.array_algos import datetimelike_accumulations
59from pandas.core.arrays import datetimelike as dtl
60from pandas.core.arrays._ranges import generate_regular_range
61import pandas.core.common as com
62from pandas.core.ops.common import unpack_zerodim_and_defer
63
64if TYPE_CHECKING:
65 from collections.abc import Iterator
66
67 from pandas._typing import (
68 AxisInt,
69 DateTimeErrorChoices,
70 DtypeObj,
71 NpDtype,
72 Self,
73 npt,
74 )
75
76 from pandas import DataFrame
77
78import textwrap
79
80
81def _field_accessor(name: str, alias: str, docstring: str):
82 def f(self) -> np.ndarray:
83 values = self.asi8
84 if alias == "days":
85 result = get_timedelta_days(values, reso=self._creso)
86 else:
87 # error: Incompatible types in assignment (
88 # expression has type "ndarray[Any, dtype[signedinteger[_32Bit]]]",
89 # variable has type "ndarray[Any, dtype[signedinteger[_64Bit]]]
90 result = get_timedelta_field(values, alias, reso=self._creso) # type: ignore[assignment]
91 if self._hasna:
92 result = self._maybe_mask_results(
93 result, fill_value=None, convert="float64"
94 )
95
96 return result
97
98 f.__name__ = name
99 f.__doc__ = f"\n{docstring}\n"
100 return property(f)
101
102
103class TimedeltaArray(dtl.TimelikeOps):
104 """
105 Pandas ExtensionArray for timedelta data.
106
107 .. warning::
108
109 TimedeltaArray is currently experimental, and its API may change
110 without warning. In particular, :attr:`TimedeltaArray.dtype` is
111 expected to change to be an instance of an ``ExtensionDtype``
112 subclass.
113
114 Parameters
115 ----------
116 values : array-like
117 The timedelta data.
118
119 dtype : numpy.dtype
120 Currently, only ``numpy.dtype("timedelta64[ns]")`` is accepted.
121 freq : Offset, optional
122 copy : bool, default False
123 Whether to copy the underlying array of data.
124
125 Attributes
126 ----------
127 None
128
129 Methods
130 -------
131 None
132
133 Examples
134 --------
135 >>> pd.arrays.TimedeltaArray._from_sequence(pd.TimedeltaIndex(['1h', '2h']))
136 <TimedeltaArray>
137 ['0 days 01:00:00', '0 days 02:00:00']
138 Length: 2, dtype: timedelta64[ns]
139 """
140
141 _typ = "timedeltaarray"
142 _internal_fill_value = np.timedelta64("NaT", "ns")
143 _recognized_scalars = (timedelta, np.timedelta64, Tick)
144 _is_recognized_dtype = lambda x: lib.is_np_dtype(x, "m")
145 _infer_matches = ("timedelta", "timedelta64")
146
147 @property
148 def _scalar_type(self) -> type[Timedelta]:
149 return Timedelta
150
151 __array_priority__ = 1000
152 # define my properties & methods for delegation
153 _other_ops: list[str] = []
154 _bool_ops: list[str] = []
155 _object_ops: list[str] = ["freq"]
156 _field_ops: list[str] = ["days", "seconds", "microseconds", "nanoseconds"]
157 _datetimelike_ops: list[str] = _field_ops + _object_ops + _bool_ops + ["unit"]
158 _datetimelike_methods: list[str] = [
159 "to_pytimedelta",
160 "total_seconds",
161 "round",
162 "floor",
163 "ceil",
164 "as_unit",
165 ]
166
167 # Note: ndim must be defined to ensure NaT.__richcmp__(TimedeltaArray)
168 # operates pointwise.
169
170 def _box_func(self, x: np.timedelta64) -> Timedelta | NaTType:
171 y = x.view("i8")
172 if y == NaT._value:
173 return NaT
174 return Timedelta._from_value_and_reso(y, reso=self._creso)
175
176 @property
177 # error: Return type "dtype" of "dtype" incompatible with return type
178 # "ExtensionDtype" in supertype "ExtensionArray"
179 def dtype(self) -> np.dtype[np.timedelta64]: # type: ignore[override]
180 """
181 The dtype for the TimedeltaArray.
182
183 .. warning::
184
185 A future version of pandas will change dtype to be an instance
186 of a :class:`pandas.api.extensions.ExtensionDtype` subclass,
187 not a ``numpy.dtype``.
188
189 Returns
190 -------
191 numpy.dtype
192 """
193 return self._ndarray.dtype
194
195 # ----------------------------------------------------------------
196 # Constructors
197
198 _freq = None
199 _default_dtype = TD64NS_DTYPE # used in TimeLikeOps.__init__
200
201 @classmethod
202 def _validate_dtype(cls, values, dtype):
203 # used in TimeLikeOps.__init__
204 dtype = _validate_td64_dtype(dtype)
205 _validate_td64_dtype(values.dtype)
206 if dtype != values.dtype:
207 raise ValueError("Values resolution does not match dtype.")
208 return dtype
209
210 # error: Signature of "_simple_new" incompatible with supertype "NDArrayBacked"
211 @classmethod
212 def _simple_new( # type: ignore[override]
213 cls,
214 values: npt.NDArray[np.timedelta64],
215 freq: Tick | None = None,
216 dtype: np.dtype[np.timedelta64] = TD64NS_DTYPE,
217 ) -> Self:
218 # Require td64 dtype, not unit-less, matching values.dtype
219 assert lib.is_np_dtype(dtype, "m")
220 assert not tslibs.is_unitless(dtype)
221 assert isinstance(values, np.ndarray), type(values)
222 assert dtype == values.dtype
223 assert freq is None or isinstance(freq, Tick)
224
225 result = super()._simple_new(values=values, dtype=dtype)
226 result._freq = freq
227 return result
228
229 @classmethod
230 def _from_sequence(cls, data, *, dtype=None, copy: bool = False) -> Self:
231 if dtype:
232 dtype = _validate_td64_dtype(dtype)
233
234 data, freq = sequence_to_td64ns(data, copy=copy, unit=None)
235
236 if dtype is not None:
237 data = astype_overflowsafe(data, dtype=dtype, copy=False)
238
239 return cls._simple_new(data, dtype=data.dtype, freq=freq)
240
241 @classmethod
242 def _from_sequence_not_strict(
243 cls,
244 data,
245 *,
246 dtype=None,
247 copy: bool = False,
248 freq=lib.no_default,
249 unit=None,
250 ) -> Self:
251 """
252 _from_sequence_not_strict but without responsibility for finding the
253 result's `freq`.
254 """
255 if dtype:
256 dtype = _validate_td64_dtype(dtype)
257
258 assert unit not in ["Y", "y", "M"] # caller is responsible for checking
259
260 data, inferred_freq = sequence_to_td64ns(data, copy=copy, unit=unit)
261
262 if dtype is not None:
263 data = astype_overflowsafe(data, dtype=dtype, copy=False)
264
265 result = cls._simple_new(data, dtype=data.dtype, freq=inferred_freq)
266
267 result._maybe_pin_freq(freq, {})
268 return result
269
270 @classmethod
271 def _generate_range(
272 cls, start, end, periods, freq, closed=None, *, unit: str | None = None
273 ) -> Self:
274 periods = dtl.validate_periods(periods)
275 if freq is None and any(x is None for x in [periods, start, end]):
276 raise ValueError("Must provide freq argument if no data is supplied")
277
278 if com.count_not_none(start, end, periods, freq) != 3:
279 raise ValueError(
280 "Of the four parameters: start, end, periods, "
281 "and freq, exactly three must be specified"
282 )
283
284 if start is not None:
285 start = Timedelta(start).as_unit("ns")
286
287 if end is not None:
288 end = Timedelta(end).as_unit("ns")
289
290 if unit is not None:
291 if unit not in ["s", "ms", "us", "ns"]:
292 raise ValueError("'unit' must be one of 's', 'ms', 'us', 'ns'")
293 else:
294 unit = "ns"
295
296 if start is not None and unit is not None:
297 start = start.as_unit(unit, round_ok=False)
298 if end is not None and unit is not None:
299 end = end.as_unit(unit, round_ok=False)
300
301 left_closed, right_closed = validate_endpoints(closed)
302
303 if freq is not None:
304 index = generate_regular_range(start, end, periods, freq, unit=unit)
305 else:
306 index = np.linspace(start._value, end._value, periods).astype("i8")
307
308 if not left_closed:
309 index = index[1:]
310 if not right_closed:
311 index = index[:-1]
312
313 td64values = index.view(f"m8[{unit}]")
314 return cls._simple_new(td64values, dtype=td64values.dtype, freq=freq)
315
316 # ----------------------------------------------------------------
317 # DatetimeLike Interface
318
319 def _unbox_scalar(self, value) -> np.timedelta64:
320 if not isinstance(value, self._scalar_type) and value is not NaT:
321 raise ValueError("'value' should be a Timedelta.")
322 self._check_compatible_with(value)
323 if value is NaT:
324 return np.timedelta64(value._value, self.unit)
325 else:
326 return value.as_unit(self.unit).asm8
327
328 def _scalar_from_string(self, value) -> Timedelta | NaTType:
329 return Timedelta(value)
330
331 def _check_compatible_with(self, other) -> None:
332 # we don't have anything to validate.
333 pass
334
335 # ----------------------------------------------------------------
336 # Array-Like / EA-Interface Methods
337
338 def astype(self, dtype, copy: bool = True):
339 # We handle
340 # --> timedelta64[ns]
341 # --> timedelta64
342 # DatetimeLikeArrayMixin super call handles other cases
343 dtype = pandas_dtype(dtype)
344
345 if lib.is_np_dtype(dtype, "m"):
346 if dtype == self.dtype:
347 if copy:
348 return self.copy()
349 return self
350
351 if is_supported_dtype(dtype):
352 # unit conversion e.g. timedelta64[s]
353 res_values = astype_overflowsafe(self._ndarray, dtype, copy=False)
354 return type(self)._simple_new(
355 res_values, dtype=res_values.dtype, freq=self.freq
356 )
357 else:
358 raise ValueError(
359 f"Cannot convert from {self.dtype} to {dtype}. "
360 "Supported resolutions are 's', 'ms', 'us', 'ns'"
361 )
362
363 return dtl.DatetimeLikeArrayMixin.astype(self, dtype, copy=copy)
364
365 def __iter__(self) -> Iterator:
366 if self.ndim > 1:
367 for i in range(len(self)):
368 yield self[i]
369 else:
370 # convert in chunks of 10k for efficiency
371 data = self._ndarray
372 length = len(self)
373 chunksize = 10000
374 chunks = (length // chunksize) + 1
375 for i in range(chunks):
376 start_i = i * chunksize
377 end_i = min((i + 1) * chunksize, length)
378 converted = ints_to_pytimedelta(data[start_i:end_i], box=True)
379 yield from converted
380
381 # ----------------------------------------------------------------
382 # Reductions
383
384 def sum(
385 self,
386 *,
387 axis: AxisInt | None = None,
388 dtype: NpDtype | None = None,
389 out=None,
390 keepdims: bool = False,
391 initial=None,
392 skipna: bool = True,
393 min_count: int = 0,
394 ):
395 nv.validate_sum(
396 (), {"dtype": dtype, "out": out, "keepdims": keepdims, "initial": initial}
397 )
398
399 result = nanops.nansum(
400 self._ndarray, axis=axis, skipna=skipna, min_count=min_count
401 )
402 return self._wrap_reduction_result(axis, result)
403
404 def std(
405 self,
406 *,
407 axis: AxisInt | None = None,
408 dtype: NpDtype | None = None,
409 out=None,
410 ddof: int = 1,
411 keepdims: bool = False,
412 skipna: bool = True,
413 ):
414 nv.validate_stat_ddof_func(
415 (), {"dtype": dtype, "out": out, "keepdims": keepdims}, fname="std"
416 )
417
418 result = nanops.nanstd(self._ndarray, axis=axis, skipna=skipna, ddof=ddof)
419 if axis is None or self.ndim == 1:
420 return self._box_func(result)
421 return self._from_backing_data(result)
422
423 # ----------------------------------------------------------------
424 # Accumulations
425
426 def _accumulate(self, name: str, *, skipna: bool = True, **kwargs):
427 if name == "cumsum":
428 op = getattr(datetimelike_accumulations, name)
429 result = op(self._ndarray.copy(), skipna=skipna, **kwargs)
430
431 return type(self)._simple_new(result, freq=None, dtype=self.dtype)
432 elif name == "cumprod":
433 raise TypeError("cumprod not supported for Timedelta.")
434
435 else:
436 return super()._accumulate(name, skipna=skipna, **kwargs)
437
438 # ----------------------------------------------------------------
439 # Rendering Methods
440
441 def _formatter(self, boxed: bool = False):
442 from pandas.io.formats.format import get_format_timedelta64
443
444 return get_format_timedelta64(self, box=True)
445
446 def _format_native_types(
447 self, *, na_rep: str | float = "NaT", date_format=None, **kwargs
448 ) -> npt.NDArray[np.object_]:
449 from pandas.io.formats.format import get_format_timedelta64
450
451 # Relies on TimeDelta._repr_base
452 formatter = get_format_timedelta64(self, na_rep)
453 # equiv: np.array([formatter(x) for x in self._ndarray])
454 # but independent of dimension
455 return np.frompyfunc(formatter, 1, 1)(self._ndarray)
456
457 # ----------------------------------------------------------------
458 # Arithmetic Methods
459
460 def _add_offset(self, other):
461 assert not isinstance(other, Tick)
462 raise TypeError(
463 f"cannot add the type {type(other).__name__} to a {type(self).__name__}"
464 )
465
466 @unpack_zerodim_and_defer("__mul__")
467 def __mul__(self, other) -> Self:
468 if is_scalar(other):
469 # numpy will accept float and int, raise TypeError for others
470 result = self._ndarray * other
471 if result.dtype.kind != "m":
472 # numpy >= 2.1 may not raise a TypeError
473 # and seems to dispatch to others.__rmul__?
474 raise TypeError(f"Cannot multiply with {type(other).__name__}")
475 freq = None
476 if self.freq is not None and not isna(other):
477 freq = self.freq * other
478 if freq.n == 0:
479 # GH#51575 Better to have no freq than an incorrect one
480 freq = None
481 return type(self)._simple_new(result, dtype=result.dtype, freq=freq)
482
483 if not hasattr(other, "dtype"):
484 # list, tuple
485 other = np.array(other)
486 if len(other) != len(self) and not lib.is_np_dtype(other.dtype, "m"):
487 # Exclude timedelta64 here so we correctly raise TypeError
488 # for that instead of ValueError
489 raise ValueError("Cannot multiply with unequal lengths")
490
491 if is_object_dtype(other.dtype):
492 # this multiplication will succeed only if all elements of other
493 # are int or float scalars, so we will end up with
494 # timedelta64[ns]-dtyped result
495 arr = self._ndarray
496 result = [arr[n] * other[n] for n in range(len(self))]
497 result = np.array(result)
498 return type(self)._simple_new(result, dtype=result.dtype)
499
500 # numpy will accept float or int dtype, raise TypeError for others
501 result = self._ndarray * other
502 if result.dtype.kind != "m":
503 # numpy >= 2.1 may not raise a TypeError
504 # and seems to dispatch to others.__rmul__?
505 raise TypeError(f"Cannot multiply with {type(other).__name__}")
506 return type(self)._simple_new(result, dtype=result.dtype)
507
508 __rmul__ = __mul__
509
510 def _scalar_divlike_op(self, other, op):
511 """
512 Shared logic for __truediv__, __rtruediv__, __floordiv__, __rfloordiv__
513 with scalar 'other'.
514 """
515 if isinstance(other, self._recognized_scalars):
516 other = Timedelta(other)
517 # mypy assumes that __new__ returns an instance of the class
518 # github.com/python/mypy/issues/1020
519 if cast("Timedelta | NaTType", other) is NaT:
520 # specifically timedelta64-NaT
521 res = np.empty(self.shape, dtype=np.float64)
522 res.fill(np.nan)
523 return res
524
525 # otherwise, dispatch to Timedelta implementation
526 return op(self._ndarray, other)
527
528 else:
529 # caller is responsible for checking lib.is_scalar(other)
530 # assume other is numeric, otherwise numpy will raise
531
532 if op in [roperator.rtruediv, roperator.rfloordiv]:
533 raise TypeError(
534 f"Cannot divide {type(other).__name__} by {type(self).__name__}"
535 )
536
537 result = op(self._ndarray, other)
538 freq = None
539
540 if self.freq is not None:
541 # Note: freq gets division, not floor-division, even if op
542 # is floordiv.
543 freq = self.freq / other
544 if freq.nanos == 0 and self.freq.nanos != 0:
545 # e.g. if self.freq is Nano(1) then dividing by 2
546 # rounds down to zero
547 freq = None
548
549 return type(self)._simple_new(result, dtype=result.dtype, freq=freq)
550
551 def _cast_divlike_op(self, other):
552 if not hasattr(other, "dtype"):
553 # e.g. list, tuple
554 other = np.array(other)
555
556 if len(other) != len(self):
557 raise ValueError("Cannot divide vectors with unequal lengths")
558 return other
559
560 def _vector_divlike_op(self, other, op) -> np.ndarray | Self:
561 """
562 Shared logic for __truediv__, __floordiv__, and their reversed versions
563 with timedelta64-dtype ndarray other.
564 """
565 # Let numpy handle it
566 result = op(self._ndarray, np.asarray(other))
567
568 if (is_integer_dtype(other.dtype) or is_float_dtype(other.dtype)) and op in [
569 operator.truediv,
570 operator.floordiv,
571 ]:
572 return type(self)._simple_new(result, dtype=result.dtype)
573
574 if op in [operator.floordiv, roperator.rfloordiv]:
575 mask = self.isna() | isna(other)
576 if mask.any():
577 result = result.astype(np.float64)
578 np.putmask(result, mask, np.nan)
579
580 return result
581
582 @unpack_zerodim_and_defer("__truediv__")
583 def __truediv__(self, other):
584 # timedelta / X is well-defined for timedelta-like or numeric X
585 op = operator.truediv
586 if is_scalar(other):
587 return self._scalar_divlike_op(other, op)
588
589 other = self._cast_divlike_op(other)
590 if (
591 lib.is_np_dtype(other.dtype, "m")
592 or is_integer_dtype(other.dtype)
593 or is_float_dtype(other.dtype)
594 ):
595 return self._vector_divlike_op(other, op)
596
597 if is_object_dtype(other.dtype):
598 other = np.asarray(other)
599 if self.ndim > 1:
600 res_cols = [left / right for left, right in zip(self, other)]
601 res_cols2 = [x.reshape(1, -1) for x in res_cols]
602 result = np.concatenate(res_cols2, axis=0)
603 else:
604 result = truediv_object_array(self._ndarray, other)
605
606 return result
607
608 else:
609 return NotImplemented
610
611 @unpack_zerodim_and_defer("__rtruediv__")
612 def __rtruediv__(self, other):
613 # X / timedelta is defined only for timedelta-like X
614 op = roperator.rtruediv
615 if is_scalar(other):
616 return self._scalar_divlike_op(other, op)
617
618 other = self._cast_divlike_op(other)
619 if lib.is_np_dtype(other.dtype, "m"):
620 return self._vector_divlike_op(other, op)
621
622 elif is_object_dtype(other.dtype):
623 # Note: unlike in __truediv__, we do not _need_ to do type
624 # inference on the result. It does not raise, a numeric array
625 # is returned. GH#23829
626 result_list = [other[n] / self[n] for n in range(len(self))]
627 return np.array(result_list)
628
629 else:
630 return NotImplemented
631
632 @unpack_zerodim_and_defer("__floordiv__")
633 def __floordiv__(self, other):
634 op = operator.floordiv
635 if is_scalar(other):
636 return self._scalar_divlike_op(other, op)
637
638 other = self._cast_divlike_op(other)
639 if (
640 lib.is_np_dtype(other.dtype, "m")
641 or is_integer_dtype(other.dtype)
642 or is_float_dtype(other.dtype)
643 ):
644 return self._vector_divlike_op(other, op)
645
646 elif is_object_dtype(other.dtype):
647 other = np.asarray(other)
648 if self.ndim > 1:
649 res_cols = [left // right for left, right in zip(self, other)]
650 res_cols2 = [x.reshape(1, -1) for x in res_cols]
651 result = np.concatenate(res_cols2, axis=0)
652 else:
653 result = floordiv_object_array(self._ndarray, other)
654
655 assert result.dtype == object
656 return result
657
658 else:
659 return NotImplemented
660
661 @unpack_zerodim_and_defer("__rfloordiv__")
662 def __rfloordiv__(self, other):
663 op = roperator.rfloordiv
664 if is_scalar(other):
665 return self._scalar_divlike_op(other, op)
666
667 other = self._cast_divlike_op(other)
668 if lib.is_np_dtype(other.dtype, "m"):
669 return self._vector_divlike_op(other, op)
670
671 elif is_object_dtype(other.dtype):
672 result_list = [other[n] // self[n] for n in range(len(self))]
673 result = np.array(result_list)
674 return result
675
676 else:
677 return NotImplemented
678
679 @unpack_zerodim_and_defer("__mod__")
680 def __mod__(self, other):
681 # Note: This is a naive implementation, can likely be optimized
682 if isinstance(other, self._recognized_scalars):
683 other = Timedelta(other)
684 return self - (self // other) * other
685
686 @unpack_zerodim_and_defer("__rmod__")
687 def __rmod__(self, other):
688 # Note: This is a naive implementation, can likely be optimized
689 if isinstance(other, self._recognized_scalars):
690 other = Timedelta(other)
691 return other - (other // self) * self
692
693 @unpack_zerodim_and_defer("__divmod__")
694 def __divmod__(self, other):
695 # Note: This is a naive implementation, can likely be optimized
696 if isinstance(other, self._recognized_scalars):
697 other = Timedelta(other)
698
699 res1 = self // other
700 res2 = self - res1 * other
701 return res1, res2
702
703 @unpack_zerodim_and_defer("__rdivmod__")
704 def __rdivmod__(self, other):
705 # Note: This is a naive implementation, can likely be optimized
706 if isinstance(other, self._recognized_scalars):
707 other = Timedelta(other)
708
709 res1 = other // self
710 res2 = other - res1 * self
711 return res1, res2
712
713 def __neg__(self) -> TimedeltaArray:
714 freq = None
715 if self.freq is not None:
716 freq = -self.freq
717 return type(self)._simple_new(-self._ndarray, dtype=self.dtype, freq=freq)
718
719 def __pos__(self) -> TimedeltaArray:
720 return type(self)._simple_new(
721 self._ndarray.copy(), dtype=self.dtype, freq=self.freq
722 )
723
724 def __abs__(self) -> TimedeltaArray:
725 # Note: freq is not preserved
726 return type(self)._simple_new(np.abs(self._ndarray), dtype=self.dtype)
727
728 # ----------------------------------------------------------------
729 # Conversion Methods - Vectorized analogues of Timedelta methods
730
731 def total_seconds(self) -> npt.NDArray[np.float64]:
732 """
733 Return total duration of each element expressed in seconds.
734
735 This method is available directly on TimedeltaArray, TimedeltaIndex
736 and on Series containing timedelta values under the ``.dt`` namespace.
737
738 Returns
739 -------
740 ndarray, Index or Series
741 When the calling object is a TimedeltaArray, the return type
742 is ndarray. When the calling object is a TimedeltaIndex,
743 the return type is an Index with a float64 dtype. When the calling object
744 is a Series, the return type is Series of type `float64` whose
745 index is the same as the original.
746
747 See Also
748 --------
749 datetime.timedelta.total_seconds : Standard library version
750 of this method.
751 TimedeltaIndex.components : Return a DataFrame with components of
752 each Timedelta.
753
754 Examples
755 --------
756 **Series**
757
758 >>> s = pd.Series(pd.to_timedelta(np.arange(5), unit='d'))
759 >>> s
760 0 0 days
761 1 1 days
762 2 2 days
763 3 3 days
764 4 4 days
765 dtype: timedelta64[ns]
766
767 >>> s.dt.total_seconds()
768 0 0.0
769 1 86400.0
770 2 172800.0
771 3 259200.0
772 4 345600.0
773 dtype: float64
774
775 **TimedeltaIndex**
776
777 >>> idx = pd.to_timedelta(np.arange(5), unit='d')
778 >>> idx
779 TimedeltaIndex(['0 days', '1 days', '2 days', '3 days', '4 days'],
780 dtype='timedelta64[ns]', freq=None)
781
782 >>> idx.total_seconds()
783 Index([0.0, 86400.0, 172800.0, 259200.0, 345600.0], dtype='float64')
784 """
785 pps = periods_per_second(self._creso)
786 return self._maybe_mask_results(self.asi8 / pps, fill_value=None)
787
788 def to_pytimedelta(self) -> npt.NDArray[np.object_]:
789 """
790 Return an ndarray of datetime.timedelta objects.
791
792 Returns
793 -------
794 numpy.ndarray
795
796 Examples
797 --------
798 >>> tdelta_idx = pd.to_timedelta([1, 2, 3], unit='D')
799 >>> tdelta_idx
800 TimedeltaIndex(['1 days', '2 days', '3 days'],
801 dtype='timedelta64[ns]', freq=None)
802 >>> tdelta_idx.to_pytimedelta()
803 array([datetime.timedelta(days=1), datetime.timedelta(days=2),
804 datetime.timedelta(days=3)], dtype=object)
805 """
806 return ints_to_pytimedelta(self._ndarray)
807
808 days_docstring = textwrap.dedent(
809 """Number of days for each element.
810
811 Examples
812 --------
813 For Series:
814
815 >>> ser = pd.Series(pd.to_timedelta([1, 2, 3], unit='d'))
816 >>> ser
817 0 1 days
818 1 2 days
819 2 3 days
820 dtype: timedelta64[ns]
821 >>> ser.dt.days
822 0 1
823 1 2
824 2 3
825 dtype: int64
826
827 For TimedeltaIndex:
828
829 >>> tdelta_idx = pd.to_timedelta(["0 days", "10 days", "20 days"])
830 >>> tdelta_idx
831 TimedeltaIndex(['0 days', '10 days', '20 days'],
832 dtype='timedelta64[ns]', freq=None)
833 >>> tdelta_idx.days
834 Index([0, 10, 20], dtype='int64')"""
835 )
836 days = _field_accessor("days", "days", days_docstring)
837
838 seconds_docstring = textwrap.dedent(
839 """Number of seconds (>= 0 and less than 1 day) for each element.
840
841 Examples
842 --------
843 For Series:
844
845 >>> ser = pd.Series(pd.to_timedelta([1, 2, 3], unit='s'))
846 >>> ser
847 0 0 days 00:00:01
848 1 0 days 00:00:02
849 2 0 days 00:00:03
850 dtype: timedelta64[ns]
851 >>> ser.dt.seconds
852 0 1
853 1 2
854 2 3
855 dtype: int32
856
857 For TimedeltaIndex:
858
859 >>> tdelta_idx = pd.to_timedelta([1, 2, 3], unit='s')
860 >>> tdelta_idx
861 TimedeltaIndex(['0 days 00:00:01', '0 days 00:00:02', '0 days 00:00:03'],
862 dtype='timedelta64[ns]', freq=None)
863 >>> tdelta_idx.seconds
864 Index([1, 2, 3], dtype='int32')"""
865 )
866 seconds = _field_accessor(
867 "seconds",
868 "seconds",
869 seconds_docstring,
870 )
871
872 microseconds_docstring = textwrap.dedent(
873 """Number of microseconds (>= 0 and less than 1 second) for each element.
874
875 Examples
876 --------
877 For Series:
878
879 >>> ser = pd.Series(pd.to_timedelta([1, 2, 3], unit='us'))
880 >>> ser
881 0 0 days 00:00:00.000001
882 1 0 days 00:00:00.000002
883 2 0 days 00:00:00.000003
884 dtype: timedelta64[ns]
885 >>> ser.dt.microseconds
886 0 1
887 1 2
888 2 3
889 dtype: int32
890
891 For TimedeltaIndex:
892
893 >>> tdelta_idx = pd.to_timedelta([1, 2, 3], unit='us')
894 >>> tdelta_idx
895 TimedeltaIndex(['0 days 00:00:00.000001', '0 days 00:00:00.000002',
896 '0 days 00:00:00.000003'],
897 dtype='timedelta64[ns]', freq=None)
898 >>> tdelta_idx.microseconds
899 Index([1, 2, 3], dtype='int32')"""
900 )
901 microseconds = _field_accessor(
902 "microseconds",
903 "microseconds",
904 microseconds_docstring,
905 )
906
907 nanoseconds_docstring = textwrap.dedent(
908 """Number of nanoseconds (>= 0 and less than 1 microsecond) for each element.
909
910 Examples
911 --------
912 For Series:
913
914 >>> ser = pd.Series(pd.to_timedelta([1, 2, 3], unit='ns'))
915 >>> ser
916 0 0 days 00:00:00.000000001
917 1 0 days 00:00:00.000000002
918 2 0 days 00:00:00.000000003
919 dtype: timedelta64[ns]
920 >>> ser.dt.nanoseconds
921 0 1
922 1 2
923 2 3
924 dtype: int32
925
926 For TimedeltaIndex:
927
928 >>> tdelta_idx = pd.to_timedelta([1, 2, 3], unit='ns')
929 >>> tdelta_idx
930 TimedeltaIndex(['0 days 00:00:00.000000001', '0 days 00:00:00.000000002',
931 '0 days 00:00:00.000000003'],
932 dtype='timedelta64[ns]', freq=None)
933 >>> tdelta_idx.nanoseconds
934 Index([1, 2, 3], dtype='int32')"""
935 )
936 nanoseconds = _field_accessor(
937 "nanoseconds",
938 "nanoseconds",
939 nanoseconds_docstring,
940 )
941
942 @property
943 def components(self) -> DataFrame:
944 """
945 Return a DataFrame of the individual resolution components of the Timedeltas.
946
947 The components (days, hours, minutes seconds, milliseconds, microseconds,
948 nanoseconds) are returned as columns in a DataFrame.
949
950 Returns
951 -------
952 DataFrame
953
954 Examples
955 --------
956 >>> tdelta_idx = pd.to_timedelta(['1 day 3 min 2 us 42 ns'])
957 >>> tdelta_idx
958 TimedeltaIndex(['1 days 00:03:00.000002042'],
959 dtype='timedelta64[ns]', freq=None)
960 >>> tdelta_idx.components
961 days hours minutes seconds milliseconds microseconds nanoseconds
962 0 1 0 3 0 0 2 42
963 """
964 from pandas import DataFrame
965
966 columns = [
967 "days",
968 "hours",
969 "minutes",
970 "seconds",
971 "milliseconds",
972 "microseconds",
973 "nanoseconds",
974 ]
975 hasnans = self._hasna
976 if hasnans:
977
978 def f(x):
979 if isna(x):
980 return [np.nan] * len(columns)
981 return x.components
982
983 else:
984
985 def f(x):
986 return x.components
987
988 result = DataFrame([f(x) for x in self], columns=columns)
989 if not hasnans:
990 result = result.astype("int64")
991 return result
992
993
994# ---------------------------------------------------------------------
995# Constructor Helpers
996
997
998def sequence_to_td64ns(
999 data,
1000 copy: bool = False,
1001 unit=None,
1002 errors: DateTimeErrorChoices = "raise",
1003) -> tuple[np.ndarray, Tick | None]:
1004 """
1005 Parameters
1006 ----------
1007 data : list-like
1008 copy : bool, default False
1009 unit : str, optional
1010 The timedelta unit to treat integers as multiples of. For numeric
1011 data this defaults to ``'ns'``.
1012 Must be un-specified if the data contains a str and ``errors=="raise"``.
1013 errors : {"raise", "coerce", "ignore"}, default "raise"
1014 How to handle elements that cannot be converted to timedelta64[ns].
1015 See ``pandas.to_timedelta`` for details.
1016
1017 Returns
1018 -------
1019 converted : numpy.ndarray
1020 The sequence converted to a numpy array with dtype ``timedelta64[ns]``.
1021 inferred_freq : Tick or None
1022 The inferred frequency of the sequence.
1023
1024 Raises
1025 ------
1026 ValueError : Data cannot be converted to timedelta64[ns].
1027
1028 Notes
1029 -----
1030 Unlike `pandas.to_timedelta`, if setting ``errors=ignore`` will not cause
1031 errors to be ignored; they are caught and subsequently ignored at a
1032 higher level.
1033 """
1034 assert unit not in ["Y", "y", "M"] # caller is responsible for checking
1035
1036 inferred_freq = None
1037 if unit is not None:
1038 unit = parse_timedelta_unit(unit)
1039
1040 data, copy = dtl.ensure_arraylike_for_datetimelike(
1041 data, copy, cls_name="TimedeltaArray"
1042 )
1043
1044 if isinstance(data, TimedeltaArray):
1045 inferred_freq = data.freq
1046
1047 # Convert whatever we have into timedelta64[ns] dtype
1048 if data.dtype == object or is_string_dtype(data.dtype):
1049 # no need to make a copy, need to convert if string-dtyped
1050 data = _objects_to_td64ns(data, unit=unit, errors=errors)
1051 copy = False
1052
1053 elif is_integer_dtype(data.dtype):
1054 # treat as multiples of the given unit
1055 data, copy_made = _ints_to_td64ns(data, unit=unit)
1056 copy = copy and not copy_made
1057
1058 elif is_float_dtype(data.dtype):
1059 # cast the unit, multiply base/frac separately
1060 # to avoid precision issues from float -> int
1061 if isinstance(data.dtype, ExtensionDtype):
1062 mask = data._mask
1063 data = data._data
1064 else:
1065 mask = np.isnan(data)
1066
1067 data = cast_from_unit_vectorized(data, unit or "ns")
1068 data[mask] = iNaT
1069 data = data.view("m8[ns]")
1070 copy = False
1071
1072 elif lib.is_np_dtype(data.dtype, "m"):
1073 if not is_supported_dtype(data.dtype):
1074 # cast to closest supported unit, i.e. s or ns
1075 new_dtype = get_supported_dtype(data.dtype)
1076 data = astype_overflowsafe(data, dtype=new_dtype, copy=False)
1077 copy = False
1078
1079 else:
1080 # This includes datetime64-dtype, see GH#23539, GH#29794
1081 raise TypeError(f"dtype {data.dtype} cannot be converted to timedelta64[ns]")
1082
1083 if not copy:
1084 data = np.asarray(data)
1085 else:
1086 data = np.array(data, copy=copy)
1087
1088 assert data.dtype.kind == "m"
1089 assert data.dtype != "m8" # i.e. not unit-less
1090
1091 return data, inferred_freq
1092
1093
1094def _ints_to_td64ns(data, unit: str = "ns"):
1095 """
1096 Convert an ndarray with integer-dtype to timedelta64[ns] dtype, treating
1097 the integers as multiples of the given timedelta unit.
1098
1099 Parameters
1100 ----------
1101 data : numpy.ndarray with integer-dtype
1102 unit : str, default "ns"
1103 The timedelta unit to treat integers as multiples of.
1104
1105 Returns
1106 -------
1107 numpy.ndarray : timedelta64[ns] array converted from data
1108 bool : whether a copy was made
1109 """
1110 copy_made = False
1111 unit = unit if unit is not None else "ns"
1112
1113 if data.dtype != np.int64:
1114 # converting to int64 makes a copy, so we can avoid
1115 # re-copying later
1116 data = data.astype(np.int64)
1117 copy_made = True
1118
1119 if unit != "ns":
1120 dtype_str = f"timedelta64[{unit}]"
1121 data = data.view(dtype_str)
1122
1123 data = astype_overflowsafe(data, dtype=TD64NS_DTYPE)
1124
1125 # the astype conversion makes a copy, so we can avoid re-copying later
1126 copy_made = True
1127
1128 else:
1129 data = data.view("timedelta64[ns]")
1130
1131 return data, copy_made
1132
1133
1134def _objects_to_td64ns(data, unit=None, errors: DateTimeErrorChoices = "raise"):
1135 """
1136 Convert a object-dtyped or string-dtyped array into an
1137 timedelta64[ns]-dtyped array.
1138
1139 Parameters
1140 ----------
1141 data : ndarray or Index
1142 unit : str, default "ns"
1143 The timedelta unit to treat integers as multiples of.
1144 Must not be specified if the data contains a str.
1145 errors : {"raise", "coerce", "ignore"}, default "raise"
1146 How to handle elements that cannot be converted to timedelta64[ns].
1147 See ``pandas.to_timedelta`` for details.
1148
1149 Returns
1150 -------
1151 numpy.ndarray : timedelta64[ns] array converted from data
1152
1153 Raises
1154 ------
1155 ValueError : Data cannot be converted to timedelta64[ns].
1156
1157 Notes
1158 -----
1159 Unlike `pandas.to_timedelta`, if setting `errors=ignore` will not cause
1160 errors to be ignored; they are caught and subsequently ignored at a
1161 higher level.
1162 """
1163 # coerce Index to np.ndarray, converting string-dtype if necessary
1164 values = np.asarray(data, dtype=np.object_)
1165
1166 result = array_to_timedelta64(values, unit=unit, errors=errors)
1167 return result.view("timedelta64[ns]")
1168
1169
1170def _validate_td64_dtype(dtype) -> DtypeObj:
1171 dtype = pandas_dtype(dtype)
1172 if dtype == np.dtype("m8"):
1173 # no precision disallowed GH#24806
1174 msg = (
1175 "Passing in 'timedelta' dtype with no precision is not allowed. "
1176 "Please pass in 'timedelta64[ns]' instead."
1177 )
1178 raise ValueError(msg)
1179
1180 if not lib.is_np_dtype(dtype, "m"):
1181 raise ValueError(f"dtype '{dtype}' is invalid, should be np.timedelta64 dtype")
1182 elif not is_supported_dtype(dtype):
1183 raise ValueError("Supported timedelta64 resolutions are 's', 'ms', 'us', 'ns'")
1184
1185 return dtype