Skip to content

EXDATE for all-day events serialized without VALUE=DATE (→ HTTP 415) #406

@madox85

Description

@madox85

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.generateUploadAndroidEventHandler.mapToVEvents.

Reproducer

  1. Create an all-day recurring event in the Android calendar (here: weekly, INTERVAL=11, BYDAY=TU).
  2. 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.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    eventsRegarding events / everything that's stored in Android Calendar Provider

    Type

    Urgency

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions