ProvenPin Install

ISO 8601 durations and datetimes with isodate: working versions (tested 2026-10-18)

Parse and format ISO 8601 durations and datetimes, use month and year Duration arithmetic with calendar clamping, and catch the parsing errors isodate raises.

passed6 passed · 0 failed2026-10-18 03:56 UTC
Status
passed
Last passed
2026-10-18 03:56 UTC
Tests
6 / 6
Language
Python
Runtime
Python 3.13.16
pinned versions2
isodate@0.7.2pytest@9.1.1
run it
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt
.venv/bin/python -m pytest -q
test environment
Python 3.13.16
linux 6.17.0-1022-azure
x64
runner: github-actions 39095370682

The code

Exactly the files that passed. Your agent can pull them with get_recipe("isodate-duration-parse") (install), or see them on GitHub.

requirements.txt
isodate==0.7.2
pytest==9.1.1
durations.py
from datetime import date, datetime, timedelta

import isodate
from isodate import Duration


def parse_duration(text: str) -> timedelta | Duration:
    return isodate.parse_duration(text)


def format_duration(value: timedelta | Duration) -> str:
    return isodate.duration_isoformat(value)


def shift(moment: date | datetime, text: str) -> date | datetime:
    return moment + isodate.parse_duration(text)


def parse_instant(text: str) -> datetime:
    return isodate.parse_datetime(text)
tests/test_durations.py
from datetime import date, datetime, timedelta

import isodate
import pytest
from durations import format_duration, parse_duration, parse_instant, shift
from isodate import Duration


def test_time_only_duration_is_a_timedelta():
    assert parse_duration("PT36H") == timedelta(hours=36)
    assert parse_duration("P2W") == timedelta(weeks=2)
    assert format_duration(timedelta(hours=36)) == "P1DT12H"


def test_month_duration_is_not_a_timedelta():
    one_month = parse_duration("P1M")
    assert isinstance(one_month, Duration)
    assert one_month == Duration(months=1)
    assert one_month != timedelta(days=30)
    assert isinstance(parse_duration("P0D"), timedelta)


def test_month_and_year_arithmetic_clamps_to_month_end():
    assert shift(datetime(2024, 1, 31), "P1M") == datetime(2024, 2, 29)
    assert shift(date(2024, 2, 29), "P1Y") == date(2025, 2, 28)


def test_full_duration_round_trips_and_zero_formats_as_p0d():
    text = "P1Y2M3DT4H5M6S"
    assert format_duration(parse_duration(text)) == text
    assert format_duration(timedelta(0)) == "P0D"


def test_datetime_z_parsing_and_date_only_input():
    assert parse_instant("2024-01-01T00:00:00Z").utcoffset() == timedelta(0)
    assert parse_instant("2024-01-01T00:00:00Z").tzinfo is isodate.UTC
    with pytest.raises(isodate.ISO8601Error):
        parse_instant("2024-01-01")
    assert isodate.parse_date("2024-01-01") == date(2024, 1, 1)


def test_invalid_durations_raise_but_pt_is_silently_zero():
    for bad in ("1Y", "P1H", "P"):
        with pytest.raises(isodate.ISO8601Error):
            parse_duration(bad)
    assert parse_duration("PT") == timedelta(0)
pytest.ini
[pytest]
pythonpath = .

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.