When parse() is given an interval made of a duration and a date-only
endpoint (e.g. "2021-01-01/P1DT1H" or "P1Y/2021-01-01"), base_parse
returns a datetime.date for the endpoint, so pendulum.instance() returns a
Date. _parse then calls Date.add()/Date.subtract() with the duration's
hours/minutes/seconds/microseconds, which Date does not accept, raising
'TypeError: add() got an unexpected keyword argument 'hours''. Endpoints
that carry a time component return a DateTime and worked fine.
Promote a date-only endpoint to a midnight datetime before instance(), so
the interval has DateTime endpoints (matching the time-bearing case and the
function's Interval[DateTime] return type) and add()/subtract() accept the
time components. Fixes python-pendulum#881.
Fixes #881.
Problem
parse() raises on an ISO 8601 interval built from a duration and a date-only endpoint:
base_parse returns a datetime.date for a date-only endpoint, so pendulum.instance() returns a Date. _parse then calls Date.add() / Date.subtract() with the duration's hours / minutes / seconds / microseconds, which Date does not accept. Endpoints that carry a time component (e.g. 2021-01-01T00:00/P1D) return a DateTime and already work.
Fix
Promote a date-only endpoint to a midnight datetime before pendulum.instance() in the two duration branches of _parse. The interval then has DateTime endpoints, matching the time-bearing case and the function's Interval[DateTime] return annotation, and add() / subtract() accept the duration's time components. Endpoints that are already datetimes are unchanged (the helper returns them as-is), and this also replaces the t.cast("datetime.datetime", parsed.end) on the end branch, which was papering over exactly this case.
Test
test_parse_interval_with_date_and_duration covers both the forward (add) and reverse (subtract) forms and asserts the resulting Interval has the expected DateTime endpoints. It raises the TypeError above on the current code and passes with this change; the existing test_parse_interval and the rest of test_parsing.py still pass (validated with PENDULUM_EXTENSIONS=0, the pure-Python parser). Happy to add a CHANGELOG entry if you'd like one.