1"""
2timedelta support tools
3"""
4from __future__ import annotations
5
6from typing import (
7 TYPE_CHECKING,
8 overload,
9)
10import warnings
11
12import numpy as np
13
14from pandas._libs import lib
15from pandas._libs.tslibs import (
16 NaT,
17 NaTType,
18)
19from pandas._libs.tslibs.timedeltas import (
20 Timedelta,
21 disallow_ambiguous_unit,
22 parse_timedelta_unit,
23)
24from pandas.util._exceptions import find_stack_level
25
26from pandas.core.dtypes.common import is_list_like
27from pandas.core.dtypes.dtypes import ArrowDtype
28from pandas.core.dtypes.generic import (
29 ABCIndex,
30 ABCSeries,
31)
32
33from pandas.core.arrays.timedeltas import sequence_to_td64ns
34
35if TYPE_CHECKING:
36 from collections.abc import Hashable
37 from datetime import timedelta
38
39 from pandas._libs.tslibs.timedeltas import UnitChoices
40 from pandas._typing import (
41 ArrayLike,
42 DateTimeErrorChoices,
43 )
44
45 from pandas import (
46 Index,
47 Series,
48 TimedeltaIndex,
49 )
50
51
52@overload
53def to_timedelta(
54 arg: str | float | timedelta,
55 unit: UnitChoices | None = ...,
56 errors: DateTimeErrorChoices = ...,
57) -> Timedelta:
58 ...
59
60
61@overload
62def to_timedelta(
63 arg: Series,
64 unit: UnitChoices | None = ...,
65 errors: DateTimeErrorChoices = ...,
66) -> Series:
67 ...
68
69
70@overload
71def to_timedelta(
72 arg: list | tuple | range | ArrayLike | Index,
73 unit: UnitChoices | None = ...,
74 errors: DateTimeErrorChoices = ...,
75) -> TimedeltaIndex:
76 ...
77
78
79def to_timedelta(
80 arg: str
81 | int
82 | float
83 | timedelta
84 | list
85 | tuple
86 | range
87 | ArrayLike
88 | Index
89 | Series,
90 unit: UnitChoices | None = None,
91 errors: DateTimeErrorChoices = "raise",
92) -> Timedelta | TimedeltaIndex | Series:
93 """
94 Convert argument to timedelta.
95
96 Timedeltas are absolute differences in times, expressed in difference
97 units (e.g. days, hours, minutes, seconds). This method converts
98 an argument from a recognized timedelta format / value into
99 a Timedelta type.
100
101 Parameters
102 ----------
103 arg : str, timedelta, list-like or Series
104 The data to be converted to timedelta.
105
106 .. versionchanged:: 2.0
107 Strings with units 'M', 'Y' and 'y' do not represent
108 unambiguous timedelta values and will raise an exception.
109
110 unit : str, optional
111 Denotes the unit of the arg for numeric `arg`. Defaults to ``"ns"``.
112
113 Possible values:
114
115 * 'W'
116 * 'D' / 'days' / 'day'
117 * 'hours' / 'hour' / 'hr' / 'h' / 'H'
118 * 'm' / 'minute' / 'min' / 'minutes' / 'T'
119 * 's' / 'seconds' / 'sec' / 'second' / 'S'
120 * 'ms' / 'milliseconds' / 'millisecond' / 'milli' / 'millis' / 'L'
121 * 'us' / 'microseconds' / 'microsecond' / 'micro' / 'micros' / 'U'
122 * 'ns' / 'nanoseconds' / 'nano' / 'nanos' / 'nanosecond' / 'N'
123
124 Must not be specified when `arg` contains strings and ``errors="raise"``.
125
126 .. deprecated:: 2.2.0
127 Units 'H', 'T', 'S', 'L', 'U' and 'N' are deprecated and will be removed
128 in a future version. Please use 'h', 'min', 's', 'ms', 'us', and 'ns'
129 instead of 'H', 'T', 'S', 'L', 'U' and 'N'.
130
131 errors : {'ignore', 'raise', 'coerce'}, default 'raise'
132 - If 'raise', then invalid parsing will raise an exception.
133 - If 'coerce', then invalid parsing will be set as NaT.
134 - If 'ignore', then invalid parsing will return the input.
135
136 Returns
137 -------
138 timedelta
139 If parsing succeeded.
140 Return type depends on input:
141
142 - list-like: TimedeltaIndex of timedelta64 dtype
143 - Series: Series of timedelta64 dtype
144 - scalar: Timedelta
145
146 See Also
147 --------
148 DataFrame.astype : Cast argument to a specified dtype.
149 to_datetime : Convert argument to datetime.
150 convert_dtypes : Convert dtypes.
151
152 Notes
153 -----
154 If the precision is higher than nanoseconds, the precision of the duration is
155 truncated to nanoseconds for string inputs.
156
157 Examples
158 --------
159 Parsing a single string to a Timedelta:
160
161 >>> pd.to_timedelta('1 days 06:05:01.00003')
162 Timedelta('1 days 06:05:01.000030')
163 >>> pd.to_timedelta('15.5us')
164 Timedelta('0 days 00:00:00.000015500')
165
166 Parsing a list or array of strings:
167
168 >>> pd.to_timedelta(['1 days 06:05:01.00003', '15.5us', 'nan'])
169 TimedeltaIndex(['1 days 06:05:01.000030', '0 days 00:00:00.000015500', NaT],
170 dtype='timedelta64[ns]', freq=None)
171
172 Converting numbers by specifying the `unit` keyword argument:
173
174 >>> pd.to_timedelta(np.arange(5), unit='s')
175 TimedeltaIndex(['0 days 00:00:00', '0 days 00:00:01', '0 days 00:00:02',
176 '0 days 00:00:03', '0 days 00:00:04'],
177 dtype='timedelta64[ns]', freq=None)
178 >>> pd.to_timedelta(np.arange(5), unit='d')
179 TimedeltaIndex(['0 days', '1 days', '2 days', '3 days', '4 days'],
180 dtype='timedelta64[ns]', freq=None)
181 """
182 if unit is not None:
183 unit = parse_timedelta_unit(unit)
184 disallow_ambiguous_unit(unit)
185
186 if errors not in ("ignore", "raise", "coerce"):
187 raise ValueError("errors must be one of 'ignore', 'raise', or 'coerce'.")
188 if errors == "ignore":
189 # GH#54467
190 warnings.warn(
191 "errors='ignore' is deprecated and will raise in a future version. "
192 "Use to_timedelta without passing `errors` and catch exceptions "
193 "explicitly instead",
194 FutureWarning,
195 stacklevel=find_stack_level(),
196 )
197
198 if arg is None:
199 return arg
200 elif isinstance(arg, ABCSeries):
201 values = _convert_listlike(arg._values, unit=unit, errors=errors)
202 return arg._constructor(values, index=arg.index, name=arg.name)
203 elif isinstance(arg, ABCIndex):
204 return _convert_listlike(arg, unit=unit, errors=errors, name=arg.name)
205 elif isinstance(arg, np.ndarray) and arg.ndim == 0:
206 # extract array scalar and process below
207 # error: Incompatible types in assignment (expression has type "object",
208 # variable has type "Union[str, int, float, timedelta, List[Any],
209 # Tuple[Any, ...], Union[Union[ExtensionArray, ndarray[Any, Any]], Index,
210 # Series]]") [assignment]
211 arg = lib.item_from_zerodim(arg) # type: ignore[assignment]
212 elif is_list_like(arg) and getattr(arg, "ndim", 1) == 1:
213 return _convert_listlike(arg, unit=unit, errors=errors)
214 elif getattr(arg, "ndim", 1) > 1:
215 raise TypeError(
216 "arg must be a string, timedelta, list, tuple, 1-d array, or Series"
217 )
218
219 if isinstance(arg, str) and unit is not None:
220 raise ValueError("unit must not be specified if the input is/contains a str")
221
222 # ...so it must be a scalar value. Return scalar.
223 return _coerce_scalar_to_timedelta_type(arg, unit=unit, errors=errors)
224
225
226def _coerce_scalar_to_timedelta_type(
227 r, unit: UnitChoices | None = "ns", errors: DateTimeErrorChoices = "raise"
228):
229 """Convert string 'r' to a timedelta object."""
230 result: Timedelta | NaTType
231
232 try:
233 result = Timedelta(r, unit)
234 except ValueError:
235 if errors == "raise":
236 raise
237 if errors == "ignore":
238 return r
239
240 # coerce
241 result = NaT
242
243 return result
244
245
246def _convert_listlike(
247 arg,
248 unit: UnitChoices | None = None,
249 errors: DateTimeErrorChoices = "raise",
250 name: Hashable | None = None,
251):
252 """Convert a list of objects to a timedelta index object."""
253 arg_dtype = getattr(arg, "dtype", None)
254 if isinstance(arg, (list, tuple)) or arg_dtype is None:
255 # This is needed only to ensure that in the case where we end up
256 # returning arg (errors == "ignore"), and where the input is a
257 # generator, we return a useful list-like instead of a
258 # used-up generator
259 if not hasattr(arg, "__array__"):
260 arg = list(arg)
261 arg = np.array(arg, dtype=object)
262 elif isinstance(arg_dtype, ArrowDtype) and arg_dtype.kind == "m":
263 return arg
264
265 try:
266 td64arr = sequence_to_td64ns(arg, unit=unit, errors=errors, copy=False)[0]
267 except ValueError:
268 if errors == "ignore":
269 return arg
270 else:
271 # This else-block accounts for the cases when errors='raise'
272 # and errors='coerce'. If errors == 'raise', these errors
273 # should be raised. If errors == 'coerce', we shouldn't
274 # expect any errors to be raised, since all parsing errors
275 # cause coercion to pd.NaT. However, if an error / bug is
276 # introduced that causes an Exception to be raised, we would
277 # like to surface it.
278 raise
279
280 from pandas import TimedeltaIndex
281
282 value = TimedeltaIndex(td64arr, name=name)
283 return value