@@ -72,19 +72,44 @@ def string_to_timedelta(x: str) -> timedelta:
7272 """
7373 Parse a duration string to a timedelta.
7474
75+ Parameters
76+ ----------
7577 x : str
7678 String representation of duration. Can use ISO 8601 duration, in
7779 a limited fashion, or a Polars-style period string.
7880 - `'PT30M'` = 30 minutes
7981 - `'P1DT6H'` = 1 day, 6 hours
8082 - `'30m'` = 30 minutes
8183 - `'3d6h30m10s'` = 3 days, 6 hours, 30 minutes, 10 seconds
84+
85+ Returns
86+ -------
87+ timedelta
88+ The parsed timedelta object.
89+
90+ Raises
91+ ------
92+ ValueError
93+ If the string cannot be parsed as a valid duration.
8294 """
83- x = x .lower ()
95+ x = x .strip (). lower ()
8496 x = x .replace ("p" , "" ).replace ("t" , "" )
85- pattern = r"(?:(?P<days>\d+)d)?(?:(?P<hours>\d+)h)?(?:(?P<minutes>\d+)m)?(?:(?P<seconds>\d+)s)?"
86-
87- groups = re .match (pattern , x ).groupdict ()
97+
98+ pattern = r"^(?:(?P<days>\d+)d)?(?:(?P<hours>\d+)h)?(?:(?P<minutes>\d+)m)?(?:(?P<seconds>\d+)s)?$"
99+
100+ match = re .match (pattern , x )
101+ if not match :
102+ raise ValueError (
103+ f"Invalid duration string: '{ x } '. "
104+ "Expected format like '3d6h30m10s' or 'PT30M'"
105+ )
106+
107+ groups = match .groupdict ()
108+
109+ # Check if at least one time component was provided
110+ if all (v is None for v in groups .values ()):
111+ raise ValueError (f"No time components found in duration string: '{ x } '" )
112+
88113 kwargs = {k : int (v ) for k , v in groups .items () if v is not None }
89114 return timedelta (** kwargs )
90115
0 commit comments