41 lines
1.4 KiB
Python
41 lines
1.4 KiB
Python
import re
|
|
from datetime import timedelta
|
|
|
|
DURATION_RE = re.compile(r"^(?:\d+[dhm])+$|^\d+$", re.IGNORECASE)
|
|
PART_RE = re.compile(r"(\d+)([dhm])", re.IGNORECASE)
|
|
DIGITS_MAX = 6
|
|
UNIT_MINUTES = {"d": 1440, "h": 60, "m": 1}
|
|
|
|
|
|
class DurationError(ValueError):
|
|
pass
|
|
|
|
|
|
def looks_like_duration(token: str) -> bool:
|
|
return bool(DURATION_RE.match(token))
|
|
|
|
|
|
def parse_duration(token: str, maximum: timedelta) -> timedelta:
|
|
if not looks_like_duration(token):
|
|
raise DurationError("Dauer nicht erkannt. Beispiele: 30m, 2h, 1d, 1h30m, 90 (Minuten).")
|
|
if any(len(number) > DIGITS_MAX for number in re.findall(r"\d+", token)):
|
|
raise DurationError("Dauer ist zu groß.")
|
|
if token.isdecimal():
|
|
minutes = int(token)
|
|
else:
|
|
minutes = sum(int(n) * UNIT_MINUTES[u.lower()] for n, u in PART_RE.findall(token))
|
|
if minutes <= 0:
|
|
raise DurationError("Die Dauer muss größer als null sein.")
|
|
duration = timedelta(minutes=minutes)
|
|
if duration > maximum:
|
|
raise DurationError(f"Die Dauer ist zu lang, erlaubt sind maximal {format_duration(maximum)}.")
|
|
return duration
|
|
|
|
|
|
def format_duration(duration: timedelta) -> str:
|
|
total = int(duration.total_seconds() // 60)
|
|
days, rest = divmod(total, 1440)
|
|
hours, minutes = divmod(rest, 60)
|
|
parts = [f"{value} {unit}" for value, unit in ((days, "d"), (hours, "h"), (minutes, "min")) if value]
|
|
return " ".join(parts) or "0 min"
|