Skip to content

Commit 1835403

Browse files
ckarnellcohen
authored andcommitted
Scale ISO 8601 duration fractions by their digit count
The pure-Python parser divided the fractional part by 10 regardless of length, so P1.25D read as 1 day plus 60 hours. Only single-digit fractions were correct, and the four components shared the assumption.
1 parent 5ad098b commit 1835403

2 files changed

Lines changed: 22 additions & 4 deletions

File tree

src/pendulum/parsing/iso8601.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ def _parse_iso8601_duration(text: str, **options: str) -> Duration | None:
296296
if "." in _weeks:
297297
_weeks, portion = _weeks.split(".")
298298
weeks = int(_weeks)
299-
_days = int(portion) / 10 * 7
299+
_days = int(portion) / 10 ** len(portion) * 7
300300
days, hours = int(_days // 1), int(_days % 1 * HOURS_PER_DAY)
301301
else:
302302
weeks = int(_weeks)
@@ -344,7 +344,7 @@ def _parse_iso8601_duration(text: str, **options: str) -> Duration | None:
344344

345345
_days, _hours = _days.split(".")
346346
days = int(_days)
347-
hours = int(_hours) / 10 * HOURS_PER_DAY
347+
hours = int(_hours) / 10 ** len(_hours) * HOURS_PER_DAY
348348
else:
349349
days = int(_days)
350350

@@ -374,7 +374,7 @@ def _parse_iso8601_duration(text: str, **options: str) -> Duration | None:
374374

375375
_hours, _mins = _hours.split(".")
376376
hours += int(_hours)
377-
minutes += int(_mins) / 10 * MINUTES_PER_HOUR
377+
minutes += int(_mins) / 10 ** len(_mins) * MINUTES_PER_HOUR
378378
else:
379379
hours += int(_hours)
380380

@@ -389,7 +389,7 @@ def _parse_iso8601_duration(text: str, **options: str) -> Duration | None:
389389

390390
_minutes, _secs = _minutes.split(".")
391391
minutes += int(_minutes)
392-
seconds += int(_secs) / 10 * SECONDS_PER_MINUTE
392+
seconds += int(_secs) / 10 ** len(_secs) * SECONDS_PER_MINUTE
393393
else:
394394
minutes += int(_minutes)
395395

tests/parsing/test_parsing_duration.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,3 +304,21 @@ def test_parse_interval_invalid():
304304
def test_parse_duration_fraction_only_allowed_on_last_component():
305305
with pytest.raises(ParserError):
306306
parse("P2Y3M4DT5.5H6M7S")
307+
308+
309+
@pytest.mark.parametrize(
310+
"text, seconds",
311+
[
312+
("P1.5D", 129600), # one fractional digit, already correct
313+
("P1.25D", 108000),
314+
("PT1.25H", 4500),
315+
("PT1.05M", 63),
316+
("P1.25W", 756000),
317+
],
318+
)
319+
def test_parse_duration_multi_digit_fraction(text, seconds):
320+
# Imports the pure-Python parser directly: `parse` uses the Rust extension
321+
# unless PENDULUM_EXTENSIONS=0, so going through it would not exercise this.
322+
from pendulum.parsing.iso8601 import parse_iso8601
323+
324+
assert parse_iso8601(text).total_seconds() == seconds

0 commit comments

Comments
 (0)