From 4891c142a999b8ff6c832a8d0a969c386152e508 Mon Sep 17 00:00:00 2001 From: sebthom Date: Fri, 25 Apr 2025 21:06:25 +0200 Subject: [PATCH] feat: add misc.format_timedelta/parse_duration --- src/kleinanzeigen_bot/utils/misc.py | 87 ++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/src/kleinanzeigen_bot/utils/misc.py b/src/kleinanzeigen_bot/utils/misc.py index 5db73a2..0393208 100644 --- a/src/kleinanzeigen_bot/utils/misc.py +++ b/src/kleinanzeigen_bot/utils/misc.py @@ -5,10 +5,12 @@ SPDX-ArtifactOfProjectHomePage: https://github.com/Second-Hand-Friends/kleinanze """ import asyncio, decimal, re, sys, time from collections.abc import Callable -from datetime import datetime +from datetime import datetime, timedelta from gettext import gettext as _ from typing import Any, TypeVar +from . import i18n + # https://mypy.readthedocs.io/en/stable/generics.html#generic-functions T = TypeVar('T') @@ -53,14 +55,19 @@ def parse_decimal(number:float | int | str) -> decimal.Decimal: """ >>> parse_decimal(5) Decimal('5') + >>> parse_decimal(5.5) Decimal('5.5') + >>> parse_decimal("5.5") Decimal('5.5') + >>> parse_decimal("5,5") Decimal('5.5') + >>> parse_decimal("1.005,5") Decimal('1005.5') + >>> parse_decimal("1,005.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)) datetime.datetime(2020, 1, 1, 0, 0) + >>> parse_datetime("2020-01-01T00:00:00") datetime.datetime(2020, 1, 1, 0, 0) + >>> parse_datetime(None) """ @@ -88,3 +97,79 @@ def parse_datetime(date:datetime | str | None) -> datetime | None: if isinstance(date, datetime): return 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)