Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GEP-2257 tweaks #2266

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 72 additions & 5 deletions kubernetes/utils/duration.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
# really be a big deal.
reDuration = re.compile(r'^([0-9]{1,5}(h|m|s|ms)){1,4}$')

# maxDuration_ms is the maximum duration that GEP-2257 can support, in
# milliseconds.
maxDuration_ms = (((99999 * 3600) + (59 * 60) + 59) * 1_000) + 999


def parse_duration(duration) -> datetime.timedelta:
"""
Parse GEP-2257 Duration format to a datetime.timedelta object.
Expand All @@ -32,17 +37,47 @@ def parse_duration(duration) -> datetime.timedelta:
See https://gateway-api.sigs.k8s.io/geps/gep-2257/ for more details.

Input: duration: string

Returns: datetime.timedelta

Raises: ValueError on invalid or unknown input

Examples:
>>> parse_duration("1h")
datetime.timedelta(seconds=3600)
>>> parse_duration("1m")
datetime.timedelta(seconds=60)
>>> parse_duration("1s")
datetime.timedelta(seconds=1)
>>> parse_duration("1ms")
datetime.timedelta(microseconds=1000)
>>> parse_duration("1h1m1s")
datetime.timedelta(seconds=3661)
>>> parse_duration("10s30m1h")
datetime.timedelta(seconds=5410)

Units are always required.
>>> parse_duration("1")
Traceback (most recent call last):
...
ValueError: Invalid duration format: 1

Floating-point and negative durations are not valid.
>>> parse_duration("1.5m")
Traceback (most recent call last):
...
ValueError: Invalid duration format: 1.5m
>>> parse_duration("-1m")
Traceback (most recent call last):
...
ValueError: Invalid duration format: -1m
"""

if not reDuration.match(duration):
raise ValueError("Invalid duration format: {}".format(duration))

return durationpy.from_str(duration)


def format_duration(delta: datetime.timedelta) -> str:
"""
Format a datetime.timedelta object to GEP-2257 Duration format.
Expand All @@ -59,12 +94,48 @@ def format_duration(delta: datetime.timedelta) -> str:

Raises: ValueError if the timedelta given cannot be expressed as a
GEP-2257 Duration.

Examples:
>>> format_duration(datetime.timedelta(seconds=3600))
'1h'
>>> format_duration(datetime.timedelta(seconds=60))
'1m'
>>> format_duration(datetime.timedelta(seconds=1))
'1s'
>>> format_duration(datetime.timedelta(microseconds=1000))
'1ms'
>>> format_duration(datetime.timedelta(seconds=5410))
'1h30m10s'

The zero duration is always "0s".
>>> format_duration(datetime.timedelta(0))
'0s'

Sub-millisecond precision is not allowed.
>>> format_duration(datetime.timedelta(microseconds=100))
Traceback (most recent call last):
...
ValueError: Cannot express sub-millisecond precision in GEP-2257: 0:00:00.000100

Negative durations are not allowed.
>>> format_duration(datetime.timedelta(seconds=-1))
Traceback (most recent call last):
...
ValueError: Cannot express negative durations in GEP-2257: -1 day, 23:59:59
"""

# Short-circuit if we have a zero delta.
if delta == datetime.timedelta(0):
return "0s"

# Check range early.
if delta < datetime.timedelta(0):
raise ValueError("Cannot express negative durations in GEP-2257: {}".format(delta))

if delta > datetime.timedelta(milliseconds=maxDuration_ms):
raise ValueError(
"Cannot express durations longer than 99999h59m59s999ms in GEP-2257: {}".format(delta))

# durationpy.to_str() is happy to use floating-point seconds, which
# GEP-2257 is _not_ happy with. So start by peeling off any microseconds
# from our delta.
Expand All @@ -90,8 +161,4 @@ def format_duration(delta: datetime.timedelta) -> str:

delta_str += f"{delta_ms}ms"

if not reDuration.match(delta_str):
raise ValueError("Invalid duration format: {}".format(durationpy.to_str(delta)))

return delta_str