1from __future__ import annotations
2
3from datetime import (
4 datetime,
5 time,
6)
7from typing import TYPE_CHECKING
8import warnings
9
10import numpy as np
11
12from pandas._libs.lib import is_list_like
13from pandas.util._exceptions import find_stack_level
14
15from pandas.core.dtypes.generic import (
16 ABCIndex,
17 ABCSeries,
18)
19from pandas.core.dtypes.missing import notna
20
21if TYPE_CHECKING:
22 from pandas._typing import DateTimeErrorChoices
23
24
25def to_time(
26 arg,
27 format: str | None = None,
28 infer_time_format: bool = False,
29 errors: DateTimeErrorChoices = "raise",
30):
31 """
32 Parse time strings to time objects using fixed strptime formats ("%H:%M",
33 "%H%M", "%I:%M%p", "%I%M%p", "%H:%M:%S", "%H%M%S", "%I:%M:%S%p",
34 "%I%M%S%p")
35
36 Use infer_time_format if all the strings are in the same format to speed
37 up conversion.
38
39 Parameters
40 ----------
41 arg : string in time format, datetime.time, list, tuple, 1-d array, Series
42 format : str, default None
43 Format used to convert arg into a time object. If None, fixed formats
44 are used.
45 infer_time_format: bool, default False
46 Infer the time format based on the first non-NaN element. If all
47 strings are in the same format, this will speed up conversion.
48 errors : {'ignore', 'raise', 'coerce'}, default 'raise'
49 - If 'raise', then invalid parsing will raise an exception
50 - If 'coerce', then invalid parsing will be set as None
51 - If 'ignore', then invalid parsing will return the input
52
53 Returns
54 -------
55 datetime.time
56 """
57 if errors == "ignore":
58 # GH#54467
59 warnings.warn(
60 "errors='ignore' is deprecated and will raise in a future version. "
61 "Use to_time without passing `errors` and catch exceptions "
62 "explicitly instead",
63 FutureWarning,
64 stacklevel=find_stack_level(),
65 )
66
67 def _convert_listlike(arg, format):
68 if isinstance(arg, (list, tuple)):
69 arg = np.array(arg, dtype="O")
70
71 elif getattr(arg, "ndim", 1) > 1:
72 raise TypeError(
73 "arg must be a string, datetime, list, tuple, 1-d array, or Series"
74 )
75
76 arg = np.asarray(arg, dtype="O")
77
78 if infer_time_format and format is None:
79 format = _guess_time_format_for_array(arg)
80
81 times: list[time | None] = []
82 if format is not None:
83 for element in arg:
84 try:
85 times.append(datetime.strptime(element, format).time())
86 except (ValueError, TypeError) as err:
87 if errors == "raise":
88 msg = (
89 f"Cannot convert {element} to a time with given "
90 f"format {format}"
91 )
92 raise ValueError(msg) from err
93 if errors == "ignore":
94 return arg
95 else:
96 times.append(None)
97 else:
98 formats = _time_formats[:]
99 format_found = False
100 for element in arg:
101 time_object = None
102 try:
103 time_object = time.fromisoformat(element)
104 except (ValueError, TypeError):
105 for time_format in formats:
106 try:
107 time_object = datetime.strptime(element, time_format).time()
108 if not format_found:
109 # Put the found format in front
110 fmt = formats.pop(formats.index(time_format))
111 formats.insert(0, fmt)
112 format_found = True
113 break
114 except (ValueError, TypeError):
115 continue
116
117 if time_object is not None:
118 times.append(time_object)
119 elif errors == "raise":
120 raise ValueError(f"Cannot convert arg {arg} to a time")
121 elif errors == "ignore":
122 return arg
123 else:
124 times.append(None)
125
126 return times
127
128 if arg is None:
129 return arg
130 elif isinstance(arg, time):
131 return arg
132 elif isinstance(arg, ABCSeries):
133 values = _convert_listlike(arg._values, format)
134 return arg._constructor(values, index=arg.index, name=arg.name)
135 elif isinstance(arg, ABCIndex):
136 return _convert_listlike(arg, format)
137 elif is_list_like(arg):
138 return _convert_listlike(arg, format)
139
140 return _convert_listlike(np.array([arg]), format)[0]
141
142
143# Fixed time formats for time parsing
144_time_formats = [
145 "%H:%M",
146 "%H%M",
147 "%I:%M%p",
148 "%I%M%p",
149 "%H:%M:%S",
150 "%H%M%S",
151 "%I:%M:%S%p",
152 "%I%M%S%p",
153]
154
155
156def _guess_time_format_for_array(arr):
157 # Try to guess the format based on the first non-NaN element
158 non_nan_elements = notna(arr).nonzero()[0]
159 if len(non_nan_elements):
160 element = arr[non_nan_elements[0]]
161 for time_format in _time_formats:
162 try:
163 datetime.strptime(element, time_format)
164 return time_format
165 except ValueError:
166 pass
167
168 return None