Summary
When DAVx⁵ uploads a recurring all-day event that has an exception date, the generated iCalendar payload emits the EXDATE property without a VALUE=DATE parameter. Per RFC 5545 §3.8.5.1, the default value type for EXDATE is DATE-TIME, so a strict CalDAV server (SabreDAV / Nextcloud) treats the bare date as a malformed DATE-TIME and rejects the PUT with HTTP 415 Unsupported Media Type. The event then stays permanently "dirty" and every sync run fails.
Environment
- DAVx⁵ 4.5.12-ose (versionCode 405120004, F-Droid build)
- ical4j 4.2.5 (as reported in
PRODID)
- Server: Nextcloud / SabreDAV
- Android calendar provider:
com.android.providers.calendar 12.7.00.7 (Samsung)
- Stack trace points at
at.bitfire.synctools.mapping.calendar.handler.RecurrenceFieldsHandler during CalendarSyncManager.generateUpload → AndroidEventHandler.mapToVEvents.
Reproducer
- Create an all-day recurring event in the Android calendar (here: weekly,
INTERVAL=11, BYDAY=TU).
- Open one instance and create an exception ("This and following" / override one occurrence). In Android this stores an
EXDATE (or an original_id exception row that is later serialized to EXDATE) plus a RECURRENCE-ID override.
- Trigger a sync to a SabreDAV/Nextcloud server.
What DAVx⁵ sends (PUT)
BEGIN:VCALENDAR
VERSION:2.0
PRODID:DAVx5/4.5.12-ose ical4j/4.2.5
BEGIN:VEVENT
DTSTAMP:20260519T195451Z
UID:<redacted>
SUMMARY:<redacted>
DTSTART;VALUE=DATE:20251216
DTEND;VALUE=DATE:20251217
RRULE:FREQ=WEEKLY;WKST=MO;COUNT=50;INTERVAL=11;BYDAY=TU
EXDATE:20251216 <-- missing ;VALUE=DATE
BEGIN:VALARM
TRIGGER:-PT7H
ACTION:DISPLAY
DESCRIPTION:<redacted>
END:VALARM
END:VEVENT
BEGIN:VEVENT
DTSTAMP:20260519T195451Z
UID:<redacted>
RECURRENCE-ID;VALUE=DATE:20260519
SUMMARY:<redacted>
DTSTART;VALUE=DATE:20260520
DTEND;VALUE=DATE:20260521
SEQUENCE:1
END:VEVENT
END:VCALENDAR
Note that DTSTART, DTEND, and RECURRENCE-ID all correctly carry VALUE=DATE, but EXDATE does not.
Server response
HTTP/1.1 415 Unsupported Media Type
Content-Type: application/xml; charset=utf-8
<?xml version="1.0" encoding="utf-8"?>
<d:error xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns">
<s:exception>Sabre\DAV\Exception\UnsupportedMediaType</s:exception>
<s:message>Validation error in iCalendar: The supplied value (20251216) is not a correct DATE-TIME</s:message>
</d:error>
SabreDAV is correct here: with no VALUE parameter, EXDATE defaults to DATE-TIME (RFC 5545 §3.8.5.1), so 20251216 is rejected because a DATE-TIME requires the YYYYMMDDTHHMMSS[Z] form.
Expected behaviour
When DTSTART is a DATE (all-day event), EXDATE must be serialized with the VALUE=DATE parameter, e.g.:
EXDATE;VALUE=DATE:20251216
This is what the RFC requires and what the analogous override on RECURRENCE-ID already does correctly in the same payload.
Stack trace
at.bitfire.dav4jvm.okhttp.exception.HttpException: HTTP 415
at at.bitfire.dav4jvm.okhttp.DavResource.checkStatus(...)
at at.bitfire.dav4jvm.okhttp.DavResource.put(...)
at at.bitfire.davdroid.sync.SyncManager$uploadDirty$4.invokeSuspend$lambda$1(...)
...
The EXDATE is built immediately before the upload by:
at.bitfire.synctools.mapping.calendar.handler.RecurrenceFieldsHandler.process
at.bitfire.synctools.mapping.calendar.AndroidEventHandler.mapEvent
at.bitfire.synctools.mapping.calendar.AndroidEventHandler.mapToVEvents
at.bitfire.davdroid.sync.CalendarSyncManager.generateUpload$core
Side observation (probably unrelated, just noting)
In the same upload run there is a non-fatal warning while parsing the exception sub-row (which has an empty rrule= field):
W RecurrenceFieldsHandler: Couldn't parse RRULE field, ignoring
java.lang.IllegalArgumentException: Missing expected token, last token:
at net.fortuna.ical4j.model.Recur.nextToken(...)
at net.fortuna.ical4j.model.Recur.<init>(...)
at net.fortuna.ical4j.model.property.RRule.setValue(...)
It would be nice if empty-string RRULEs were skipped without involving ical4j's parser, but it's only a log warning and not the cause of the 415.
Related but distinct prior issues
I could not find an existing issue covering this serialization direction.
Debug bundle
Full debug info / logcat available on request. The key entry can be located by searching the logs for the event UID.
Summary
When DAVx⁵ uploads a recurring all-day event that has an exception date, the generated iCalendar payload emits the
EXDATEproperty without aVALUE=DATEparameter. Per RFC 5545 §3.8.5.1, the default value type forEXDATEisDATE-TIME, so a strict CalDAV server (SabreDAV / Nextcloud) treats the bare date as a malformedDATE-TIMEand rejects thePUTwithHTTP 415 Unsupported Media Type. The event then stays permanently "dirty" and every sync run fails.Environment
PRODID)com.android.providers.calendar12.7.00.7 (Samsung)at.bitfire.synctools.mapping.calendar.handler.RecurrenceFieldsHandlerduringCalendarSyncManager.generateUpload→AndroidEventHandler.mapToVEvents.Reproducer
INTERVAL=11,BYDAY=TU).EXDATE(or anoriginal_idexception row that is later serialized toEXDATE) plus aRECURRENCE-IDoverride.What DAVx⁵ sends (
PUT)Note that
DTSTART,DTEND, andRECURRENCE-IDall correctly carryVALUE=DATE, butEXDATEdoes not.Server response
SabreDAV is correct here: with no
VALUEparameter,EXDATEdefaults toDATE-TIME(RFC 5545 §3.8.5.1), so20251216is rejected because aDATE-TIMErequires theYYYYMMDDTHHMMSS[Z]form.Expected behaviour
When
DTSTARTis aDATE(all-day event),EXDATEmust be serialized with theVALUE=DATEparameter, e.g.:This is what the RFC requires and what the analogous override on
RECURRENCE-IDalready does correctly in the same payload.Stack trace
The
EXDATEis built immediately before the upload by:Side observation (probably unrelated, just noting)
In the same upload run there is a non-fatal warning while parsing the exception sub-row (which has an empty
rrule=field):It would be nice if empty-string RRULEs were skipped without involving ical4j's parser, but it's only a log warning and not the cause of the 415.
Related but distinct prior issues
EXDATEto recurring event using UTC #328 —EXDATEgettingTZID=UTCon UTCDATE-TIMEevents. Opposite direction, doesn't cover the all-dayDATEcase.EXDATE;TZID=…;VALUE=DATEfrom Thunderbird (parsing side).EXDATE;VALUE=DATEfrom other clients.I could not find an existing issue covering this serialization direction.
Debug bundle
Full debug info / logcat available on request. The key entry can be located by searching the logs for the event UID.