Skip to content

Instantly share code, notes, and snippets.

@ddelange
Last active November 15, 2024 15:20
Show Gist options
  • Save ddelange/20e819b29793f422f4e53e968ad54276 to your computer and use it in GitHub Desktop.
Save ddelange/20e819b29793f422f4e53e968ad54276 to your computer and use it in GitHub Desktop.
Parse a GEP-2257 like duration string into a Python timedelta
import re
from datetime import timedelta
# Ordered case-insensitive duration strings, loosely based on the [GEP-2257](https://gateway-api.sigs.k8s.io/geps/gep-2257/) spec
# Discrepancies between this pattern and GEP-2257 duration strings:
# - this pattern accepts units `w|d|h|m|s|ms|[uµ]s` (all units supported by the datetime.timedelta constructor), GEP-2257 accepts only `h|m|s|ms`
# - this pattern allows for optional whitespace around the units, GEP-2257 does not
# - this pattern is compiled to be case-insensitive, GEP-2257 expects lowercase units
# - this pattern expects ordered (descending) units, GEP-2257 allows arbitrary order
# - this pattern does not allow duplicate unit occurrences, GEP-2257 does
# - this pattern allows for negative integers, GEP-2257 does not
_TIMEDELTA_PATTERN = re.compile(
r"^(?:\s*)" # optional whitespace at the beginning of the string
r"(?:(-?\d+)\s*w\s*)?" # weeks with optional whitespace around unit
r"(?:(-?\d+)\s*d\s*)?" # days with optional whitespace around unit
r"(?:(-?\d+)\s*h\s*)?" # hours with optional whitespace around unit
r"(?:(-?\d+)\s*m\s*)?" # minutes with optional whitespace around unit
r"(?:(-?\d+)\s*s\s*)?" # seconds with optional whitespace around unit
r"(?:(-?\d+)\s*ms\s*)?" # milliseconds with optional whitespace around unit
r"(?:(-?\d+)\s*[µu]s\s*)?$", # microseconds with optional whitespace around unit
flags=re.IGNORECASE,
)
def parse_duration_string(value: str) -> timedelta | None:
"""Parse a GEP-2257 like duration string into a timedelta.
>>> parse_duration_string('42w 42d 42h 42m 42s 42ms 42us')
datetime.timedelta(days=337, seconds=67362, microseconds=42042)
"""
match = _TIMEDELTA_PATTERN.match(value)
if match is not None and any(groups := match.groups(default=0)):
return timedelta(
weeks=int(groups[0]),
days=int(groups[1]),
hours=int(groups[2]),
minutes=int(groups[3]),
seconds=int(groups[4]),
milliseconds=int(groups[5]),
microseconds=int(groups[6]),
)
parse_duration_string('5w -7us')
@ddelange
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment