Skip to content

Commit

Permalink
parse_timedelta acts exacty the same as previous library
Browse files Browse the repository at this point in the history
  • Loading branch information
svrooij committed Jan 15, 2025
1 parent fc6c719 commit 431b036
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 68 deletions.
79 changes: 42 additions & 37 deletions packages/abstractions/kiota_abstractions/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,49 +41,54 @@ def lazy_import(name):
return module


# https://en.wikipedia.org/wiki/ISO_8601#Durations
# PnYnMnDTnHnMnS
# PnW
# P<date>T<time>
_ISO8601_DURATION_PATTERN = re.compile(
r'P' # starts with 'P'
r'(?:(\d+)Y)?' # years
r'(?:(\d+)M)?' # months
r'(?:(\d+)W)?' # weeks
r'(?:(\d+)D)?' # days
r'(?:T' # time part starts with 'T'
r'(?:(\d+)H)?' # hours
r'(?:(\d+)M)?' # minutes
r'(?:(\d+)S)?)?' # seconds
"^P" # Duration P indicator
# Weeks
"(?P<w>"
r" (?P<weeks>\d+(?:[.,]\d+)?W)"
")?"
# Years, Months, Days
"(?P<ymd>"
r" (?P<years>\d+(?:[.,]\d+)?Y)?"
r" (?P<months>\d+(?:[.,]\d+)?M)?"
r" (?P<days>\d+(?:[.,]\d+)?D)?"
")?"
# Time
"(?P<hms>"
" (?P<timesep>T)" # Separator (T)
r" (?P<hours>\d+(?:[.,]\d+)?H)?"
r" (?P<minutes>\d+(?:[.,]\d+)?M)?"
r" (?P<seconds>\d+(?:[.,]\d+)?S)?"
")?"
"$",
re.VERBOSE,
)


def parseTimeDeltaFromIsoFormat(duration_str):
"""Parses an ISO 8601 duration string into a timedelta object.
def parse_timedelta_from_iso_format(text: str) -> timedelta | None:
m = _ISO8601_DURATION_PATTERN.match(text)
if not m:
return None

https://en.wikipedia.org/wiki/ISO_8601#Durations
PnYnMnDTnHnMnS (where n is a number, supported)
PnW (weeks, supported)
P<date>T<time> (not implemented)
weeks = float(m.group("weeks").replace(",", ".").replace("W", "")) if m.group("weeks") else 0
years = float(m.group("years").replace(",", ".").replace("Y", "")) if m.group("years") else 0
months = float(m.group("months").replace(",", ".").replace("M", "")) if m.group("months") else 0
days = float(m.group("days").replace(",", ".").replace("D", "")) if m.group("days") else 0
hours = float(m.group("hours").replace(",", ".").replace("H", "")) if m.group("hours") else 0
minutes = float(m.group("minutes").replace(",", ".").replace("M", "")
) if m.group("minutes") else 0
seconds = float(m.group("seconds").replace(",", ".").replace("S", "")
) if m.group("seconds") else 0

Args:
duration_str (str): The ISO 8601 duration string.
Returns:
timedelta: The parsed timedelta object.
"""
pattern = _ISO8601_DURATION_PATTERN
match = pattern.fullmatch(duration_str)
if not match:
raise ValueError(f"Invalid ISO 8601 duration string: {duration_str}")
if weeks and (years or months or days or hours or minutes or seconds):
raise ValueError("Invalid duration string")

years, months, weeks, days, hours, minutes, seconds = match.groups()
return timedelta(
years=int(years or 0),
months=int(months or 0),
weeks=int(weeks or 0),
days=int(days or 0),
hours=int(hours or 0),
minutes=int(minutes or 0),
seconds=int(seconds or 0)
years=years,
months=months,
weeks=weeks,
days=days,
hours=hours,
minutes=minutes,
seconds=seconds
)
51 changes: 28 additions & 23 deletions packages/abstractions/tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,51 +1,56 @@
import pytest

from kiota_abstractions.utils import parseTimeDeltaFromIsoFormat
from kiota_abstractions.utils import parse_timedelta_from_iso_format


def test_parseTimeDeltaFromIsoFormat_weeks():
result = parseTimeDeltaFromIsoFormat("P3W")
def test_parse_timedelta_from_iso_format_weeks():
result = parse_timedelta_from_iso_format("P3W")
assert result.days == 21


def test_parseTimeDeltaFromIsoFormat_days():
result = parseTimeDeltaFromIsoFormat("P3D")
def test_parse_timedelta_from_iso_format_days():
result = parse_timedelta_from_iso_format("P3D")
assert result.days == 3


def test_parseTimeDeltaFromIsoFormat_hours():
result = parseTimeDeltaFromIsoFormat("PT3H")
def test_parse_timedelta_from_iso_format_hours():
result = parse_timedelta_from_iso_format("PT3H")
assert result.seconds == 10800


def test_parseTimeDeltaFromIsoFormat_minutes():
result = parseTimeDeltaFromIsoFormat("PT3M")
def test_parse_timedelta_from_iso_format_minutes():
result = parse_timedelta_from_iso_format("PT3M")
assert result.seconds == 180


def test_parseTimeDeltaFromIsoFormat_seconds():
result = parseTimeDeltaFromIsoFormat("PT3S")
def test_parse_timedelta_from_iso_format_seconds():
result = parse_timedelta_from_iso_format("PT3S")
assert result.seconds == 3


def test_parseTimeDeltaFromIsoFormat_years():
result = parseTimeDeltaFromIsoFormat("P3Y")
def test_parse_timedelta_from_iso_format_years():
result = parse_timedelta_from_iso_format("P3Y")
assert result.days == 1095


def test_parseTimeDeltaFromIsoFormat_months():
result = parseTimeDeltaFromIsoFormat("P3M")
def test_parse_timedelta_from_iso_format_months():
result = parse_timedelta_from_iso_format("P3M")
assert result.days == 90

# This is "invalid" according to the ISO 8601 standard, but python also supports it

def test_parse_timedelta_from_iso_format_days_and_time():
result = parse_timedelta_from_iso_format("P3DT3H3M3S")
assert result.days == 3
assert result.seconds == 10983

def test_parseTimeDeltaFromIsoFormat_weeks_and_years():
result = parseTimeDeltaFromIsoFormat("P3Y3W")
assert result.days == 1122

def test_parse_timedelta_from_iso_format_weeks_and_years():
# assert this raises a ValueError
with pytest.raises(ValueError):
parse_timedelta_from_iso_format("P3W3Y")

def test_parseTimeDeltaFromIsoFormat_days_and_time():
result = parseTimeDeltaFromIsoFormat("P3DT3H3M3S")
assert result.days == 3
assert result.seconds == 10983

def test_parse_timedelta_from_iso_format_years_and_weeks():
# assert this raises a ValueError
with pytest.raises(ValueError):
parse_timedelta_from_iso_format("P3Y3W")
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from urllib.parse import unquote_plus
from uuid import UUID

from kiota_abstractions.utils import parseTimeDeltaFromIsoFormat
from kiota_abstractions.utils import parse_timedelta_from_iso_format
from kiota_abstractions.serialization import Parsable, ParsableFactory, ParseNode

T = TypeVar("T", bool, str, int, float, UUID,
Expand Down Expand Up @@ -108,7 +108,7 @@ def get_timedelta_value(self) -> Optional[timedelta]:
"""
if self._node and self._node != "null":
try:
return parseTimeDeltaFromIsoFormat(self._node)
return parse_timedelta_from_iso_format(self._node)
except:
return None
return None
Expand Down Expand Up @@ -323,7 +323,7 @@ def try_get_anything(self, value: Any) -> Any:
except ValueError:
pass
try:
return parseTimeDeltaFromIsoFormat(value)
return parse_timedelta_from_iso_format(value)
except ValueError:
pass
try:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from typing import Any, Optional, TypeVar
from uuid import UUID

from kiota_abstractions.utils import parseTimeDeltaFromIsoFormat
from kiota_abstractions.utils import parse_timedelta_from_iso_format
from kiota_abstractions.serialization import Parsable, ParsableFactory, ParseNode

T = TypeVar("T", bool, str, int, float, UUID,
Expand Down Expand Up @@ -113,7 +113,7 @@ def get_timedelta_value(self) -> Optional[timedelta]:
return self._json_node
if isinstance(self._json_node, str):
try:
return parseTimeDeltaFromIsoFormat(self._json_node)
return parse_timedelta_from_iso_format(self._json_node)
except ValueError:
return None
return None
Expand Down Expand Up @@ -334,7 +334,7 @@ def try_get_anything(self, value: Any) -> Any:
except:
pass
try:
return parseTimeDeltaFromIsoFormat(value)
return parse_timedelta_from_iso_format(value)
except ValueError:
pass
try:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from typing import Optional, TypeVar
from uuid import UUID

from kiota_abstractions.utils import parseTimeDeltaFromIsoFormat
from kiota_abstractions.utils import parse_timedelta_from_iso_format
from kiota_abstractions.serialization import Parsable, ParsableFactory, ParseNode

T = TypeVar("T", bool, str, int, float, UUID, datetime, timedelta, date, time, bytes)
Expand Down Expand Up @@ -105,7 +105,7 @@ def get_timedelta_value(self) -> Optional[timedelta]:
"""
datetime_str = self.get_str_value()
if datetime_str:
return parseTimeDeltaFromIsoFormat(datetime_str)
return parse_timedelta_from_iso_format(datetime_str)
return None

def get_date_value(self) -> Optional[date]:
Expand Down

0 comments on commit 431b036

Please sign in to comment.