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