Loading src/main/java/at/bitfire/ical4android/AndroidEvent.kt +4 −4 Original line number Diff line number Diff line Loading @@ -573,10 +573,10 @@ abstract class AndroidEvent( (it checks for RRULE and aborts if no RRULE is found). So I have chosen the method of inserting the exception event manually. It's also noteworthy that the link between the main event and the exception is not between ID and ORIGINAL_ID (as one could assume), but between _SYNC_ID and ORIGINAL_SYNC_ID. So, if you don't set _SYNC_ID in the master event and ORIGINAL_SYNC_ID in the exception, the exception will appear additionally (and not *instead* of the instance). It's also noteworthy that linking the main event to the exception only works using _SYNC_ID and ORIGINAL_SYNC_ID (and not ID and ORIGINAL_ID, as one could assume). So, if you don't set _SYNC_ID in the main event and ORIGINAL_SYNC_ID in the exception, the exception will appear additionally (and not *instead* of the instance). */ val recurrenceId = exception.recurrenceId Loading src/main/java/at/bitfire/ical4android/Event.kt +18 −3 Original line number Diff line number Diff line Loading @@ -94,7 +94,6 @@ class Event: ICalendar() { Ical4Android.log.fine("Assigning exceptions to main events") val mainEvents = mutableMapOf<String /* UID */,VEvent>() val exceptions = mutableMapOf<String /* UID */,MutableMap<String /* RECURRENCE-ID */,VEvent>>() for (vEvent in vEvents) { val uid = vEvent.uid.value val sequence = vEvent.sequence?.sequenceNo ?: 0 Loading Loading @@ -124,11 +123,17 @@ class Event: ICalendar() { } } /* There may be UIDs which have only RECURRENCE-ID entries and not a main entry (for instance, a recurring event with an exception where the current user has been invited only to this exception. In this case, the UID will not appear in mainEvents but only in exceptions. */ val events = mutableListOf<Event>() for ((uid, vEvent) in mainEvents) { val event = fromVEvent(vEvent) exceptions[uid]?.let { eventExceptions -> event.exceptions.addAll(eventExceptions.map { (_,it) -> fromVEvent(it) }) // assign exceptions to main event and then remove them from exceptions array exceptions.remove(uid)?.let { eventExceptions -> event.exceptions.addAll(eventExceptions.values.map { fromVEvent(it) }) } // make sure that exceptions have at least a SUMMARY Loading @@ -137,6 +142,16 @@ class Event: ICalendar() { events += event } for ((uid, onlyExceptions) in exceptions) { Ical4Android.log.info("UID $uid doesn't have a main event but only exceptions: $onlyExceptions") // create a fake main event from the first exception val fakeEvent = fromVEvent(onlyExceptions.values.first()) fakeEvent.exceptions.addAll(onlyExceptions.values.map { fromVEvent(it) }) events += fakeEvent } return events } Loading src/test/java/at/bitfire/ical4android/EventTest.kt +15 −0 Original line number Diff line number Diff line Loading @@ -157,6 +157,21 @@ class EventTest { assertEquals("Another summary for the third day", exception.summary) } @Test fun testRecurringOnlyException() { val event = parseCalendar("recurring-only-exception.ics").first() assertEquals(1, event.exceptions.size) val exception = event.exceptions.first assertEquals("20150503T010203Z", exception.recurrenceId!!.value) assertEquals("This is an exception", exception.summary) // fake main event assertEquals(event.summary, exception.summary) assertEquals(event.dtStart, exception.dtStart) assertEquals(event.dtEnd, exception.dtEnd) } @Test fun testStartEndTimes() { // event with start+end date-time Loading src/test/resources/events/recurring-only-exception.ics 0 → 100644 +11 −0 Original line number Diff line number Diff line BEGIN:VCALENDAR VERSION:2.0 BEGIN:VEVENT UID:fcb42e4d-bc6e-4499-97f0-6616a02da7bc RECURRENCE-ID:20150503T010203Z DTSTART:20150503T010203Z DTEND:20150504T010203Z SUMMARY:This is an exception DESCRIPTION:The main event is not visible for us. END:VEVENT END:VCALENDAR Loading
src/main/java/at/bitfire/ical4android/AndroidEvent.kt +4 −4 Original line number Diff line number Diff line Loading @@ -573,10 +573,10 @@ abstract class AndroidEvent( (it checks for RRULE and aborts if no RRULE is found). So I have chosen the method of inserting the exception event manually. It's also noteworthy that the link between the main event and the exception is not between ID and ORIGINAL_ID (as one could assume), but between _SYNC_ID and ORIGINAL_SYNC_ID. So, if you don't set _SYNC_ID in the master event and ORIGINAL_SYNC_ID in the exception, the exception will appear additionally (and not *instead* of the instance). It's also noteworthy that linking the main event to the exception only works using _SYNC_ID and ORIGINAL_SYNC_ID (and not ID and ORIGINAL_ID, as one could assume). So, if you don't set _SYNC_ID in the main event and ORIGINAL_SYNC_ID in the exception, the exception will appear additionally (and not *instead* of the instance). */ val recurrenceId = exception.recurrenceId Loading
src/main/java/at/bitfire/ical4android/Event.kt +18 −3 Original line number Diff line number Diff line Loading @@ -94,7 +94,6 @@ class Event: ICalendar() { Ical4Android.log.fine("Assigning exceptions to main events") val mainEvents = mutableMapOf<String /* UID */,VEvent>() val exceptions = mutableMapOf<String /* UID */,MutableMap<String /* RECURRENCE-ID */,VEvent>>() for (vEvent in vEvents) { val uid = vEvent.uid.value val sequence = vEvent.sequence?.sequenceNo ?: 0 Loading Loading @@ -124,11 +123,17 @@ class Event: ICalendar() { } } /* There may be UIDs which have only RECURRENCE-ID entries and not a main entry (for instance, a recurring event with an exception where the current user has been invited only to this exception. In this case, the UID will not appear in mainEvents but only in exceptions. */ val events = mutableListOf<Event>() for ((uid, vEvent) in mainEvents) { val event = fromVEvent(vEvent) exceptions[uid]?.let { eventExceptions -> event.exceptions.addAll(eventExceptions.map { (_,it) -> fromVEvent(it) }) // assign exceptions to main event and then remove them from exceptions array exceptions.remove(uid)?.let { eventExceptions -> event.exceptions.addAll(eventExceptions.values.map { fromVEvent(it) }) } // make sure that exceptions have at least a SUMMARY Loading @@ -137,6 +142,16 @@ class Event: ICalendar() { events += event } for ((uid, onlyExceptions) in exceptions) { Ical4Android.log.info("UID $uid doesn't have a main event but only exceptions: $onlyExceptions") // create a fake main event from the first exception val fakeEvent = fromVEvent(onlyExceptions.values.first()) fakeEvent.exceptions.addAll(onlyExceptions.values.map { fromVEvent(it) }) events += fakeEvent } return events } Loading
src/test/java/at/bitfire/ical4android/EventTest.kt +15 −0 Original line number Diff line number Diff line Loading @@ -157,6 +157,21 @@ class EventTest { assertEquals("Another summary for the third day", exception.summary) } @Test fun testRecurringOnlyException() { val event = parseCalendar("recurring-only-exception.ics").first() assertEquals(1, event.exceptions.size) val exception = event.exceptions.first assertEquals("20150503T010203Z", exception.recurrenceId!!.value) assertEquals("This is an exception", exception.summary) // fake main event assertEquals(event.summary, exception.summary) assertEquals(event.dtStart, exception.dtStart) assertEquals(event.dtEnd, exception.dtEnd) } @Test fun testStartEndTimes() { // event with start+end date-time Loading
src/test/resources/events/recurring-only-exception.ics 0 → 100644 +11 −0 Original line number Diff line number Diff line BEGIN:VCALENDAR VERSION:2.0 BEGIN:VEVENT UID:fcb42e4d-bc6e-4499-97f0-6616a02da7bc RECURRENCE-ID:20150503T010203Z DTSTART:20150503T010203Z DTEND:20150504T010203Z SUMMARY:This is an exception DESCRIPTION:The main event is not visible for us. END:VEVENT END:VCALENDAR