times.py 4.8 KB

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