The share-by-map BDAY-to-ICS conversion builds a placeholder mapping for
the SUMMARY/DESCRIPTION/alarm templates. Each block sets the fallback
marker for its own placeholder (e.g. {nickname} -> '!nickname!', {n:f} ->
'!n:f!'), but the {fn} block wrote the empty-FN fallback to {nickname}
instead of {fn}.
As a result, for a VCARD whose FN is present but empty, a genuine
NICKNAME was overwritten with '!fn!' in the generated event, and the
{fn} placeholder never received its '!fn!' marker, so a '[{fn}|...]'
fallback in a template failed to resolve.
Assign the fallback to {fn}, matching the surrounding blocks.
Co-Authored-By: Claude <noreply@anthropic.com>
A CardDAV addressbook-query REPORT with a text-match prop-filter on a
structured property (e.g. N or ADR) returned HTTP 500. vobject parses
these into Name/Address objects rather than plain strings, so text_match
called .lower() on a non-string and raised AttributeError. Coerce
non-string values to their text representation before matching.
Co-Authored-By: Claude <noreply@anthropic.com>
A calendar-query REPORT with a time-range filter failed to return a
VEVENT that has a whole-day DURATION (e.g. DURATION:P1D or P2D) whenever
the queried range fell inside the event but after DTSTART.
The VEVENT time-range logic in radicale/item/filter.py gated the
"non-zero duration" branch (rfc4791-9.9 line 2) on timedelta.seconds
instead of timedelta.total_seconds(). For a duration that is an exact
multiple of 24h, timedelta.seconds is 0 (the days component holds the
value), so the event was treated as zero-length (line 3) and only
matched a one-second window at its start. An identical event expressed
with DTEND matched correctly, confirming the defect is isolated to the
DURATION path.
Use total_seconds() so multi-day durations are handled correctly. Adds a
regression test (event11, DURATION:P2D) covering both an inside-range
match and an outside-range non-match.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>