feat: add misc.format_timedelta/parse_duration

This commit is contained in:
sebthom
2025-04-25 21:06:25 +02:00
parent e417750548
commit 4891c142a9

View File

@@ -5,10 +5,12 @@ SPDX-ArtifactOfProjectHomePage: https://github.com/Second-Hand-Friends/kleinanze
""" """
import asyncio, decimal, re, sys, time import asyncio, decimal, re, sys, time
from collections.abc import Callable from collections.abc import Callable
from datetime import datetime from datetime import datetime, timedelta
from gettext import gettext as _ from gettext import gettext as _
from typing import Any, TypeVar from typing import Any, TypeVar
from . import i18n
# https://mypy.readthedocs.io/en/stable/generics.html#generic-functions # https://mypy.readthedocs.io/en/stable/generics.html#generic-functions
T = TypeVar('T') T = TypeVar('T')
@@ -53,14 +55,19 @@ def parse_decimal(number:float | int | str) -> decimal.Decimal:
""" """
>>> parse_decimal(5) >>> parse_decimal(5)
Decimal('5') Decimal('5')
>>> parse_decimal(5.5) >>> parse_decimal(5.5)
Decimal('5.5') Decimal('5.5')
>>> parse_decimal("5.5") >>> parse_decimal("5.5")
Decimal('5.5') Decimal('5.5')
>>> parse_decimal("5,5") >>> parse_decimal("5,5")
Decimal('5.5') Decimal('5.5')
>>> parse_decimal("1.005,5") >>> parse_decimal("1.005,5")
Decimal('1005.5') Decimal('1005.5')
>>> parse_decimal("1,005.5") >>> parse_decimal("1,005.5")
Decimal('1005.5') Decimal('1005.5')
""" """
@@ -78,8 +85,10 @@ def parse_datetime(date:datetime | str | None) -> datetime | None:
""" """
>>> parse_datetime(datetime(2020, 1, 1, 0, 0)) >>> parse_datetime(datetime(2020, 1, 1, 0, 0))
datetime.datetime(2020, 1, 1, 0, 0) datetime.datetime(2020, 1, 1, 0, 0)
>>> parse_datetime("2020-01-01T00:00:00") >>> parse_datetime("2020-01-01T00:00:00")
datetime.datetime(2020, 1, 1, 0, 0) datetime.datetime(2020, 1, 1, 0, 0)
>>> parse_datetime(None) >>> parse_datetime(None)
""" """
@@ -88,3 +97,79 @@ def parse_datetime(date:datetime | str | None) -> datetime | None:
if isinstance(date, datetime): if isinstance(date, datetime):
return date return date
return datetime.fromisoformat(date) return datetime.fromisoformat(date)
def parse_duration(text:str) -> timedelta:
"""
Parses a human-readable duration string into a datetime.timedelta.
Supported units:
- d: days
- h: hours
- m: minutes
- s: seconds
Examples:
>>> parse_duration("1h 30m")
datetime.timedelta(seconds=5400)
>>> parse_duration("2d 4h 15m 10s")
datetime.timedelta(days=2, seconds=15310)
>>> parse_duration("45m")
datetime.timedelta(seconds=2700)
>>> parse_duration("3d")
datetime.timedelta(days=3)
>>> parse_duration("5h 5h")
datetime.timedelta(seconds=36000)
>>> parse_duration("invalid input")
datetime.timedelta(0)
"""
pattern = re.compile(r'(\d+)\s*([dhms])')
parts = pattern.findall(text.lower())
kwargs: dict[str, int] = {}
for value, unit in parts:
if unit == 'd':
kwargs['days'] = kwargs.get('days', 0) + int(value)
elif unit == 'h':
kwargs['hours'] = kwargs.get('hours', 0) + int(value)
elif unit == 'm':
kwargs['minutes'] = kwargs.get('minutes', 0) + int(value)
elif unit == 's':
kwargs['seconds'] = kwargs.get('seconds', 0) + int(value)
return timedelta(**kwargs)
def format_timedelta(td: timedelta) -> str:
"""
Formats a timedelta into a human-readable string using the pluralize utility.
>>> format_timedelta(timedelta(seconds=90))
'1 minute, 30 seconds'
>>> format_timedelta(timedelta(hours=1))
'1 hour'
>>> format_timedelta(timedelta(days=2, hours=5))
'2 days, 5 hours'
>>> format_timedelta(timedelta(0))
'0 seconds'
"""
days = td.days
seconds = td.seconds
hours, remainder = divmod(seconds, 3600)
minutes, seconds = divmod(remainder, 60)
parts = []
if days:
parts.append(i18n.pluralize("day", days))
if hours:
parts.append(i18n.pluralize("hour", hours))
if minutes:
parts.append(i18n.pluralize("minute", minutes))
if seconds:
parts.append(i18n.pluralize("second", seconds))
return ", ".join(parts) if parts else i18n.pluralize("second", 0)