From 267765611efdeb610493851d72f60d629ba8dc01 Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Sat, 24 Oct 2020 13:32:12 +0200 Subject: [PATCH] Remove unnecessary X- properties in sent VTIMEZONEs (for libical compatibility) See https://github.com/libical/libical/pull/446 --- .../java/at/bitfire/ical4android/Event.kt | 8 +- .../java/at/bitfire/ical4android/ICalendar.kt | 123 +++++++++--------- src/main/java/at/bitfire/ical4android/Task.kt | 8 +- 3 files changed, 64 insertions(+), 75 deletions(-) diff --git a/src/main/java/at/bitfire/ical4android/Event.kt b/src/main/java/at/bitfire/ical4android/Event.kt index 601cb4c..8469153 100644 --- a/src/main/java/at/bitfire/ical4android/Event.kt +++ b/src/main/java/at/bitfire/ical4android/Event.kt @@ -256,12 +256,8 @@ class Event: ICalendar() { dtStarts.addAll(exceptions.mapNotNull { it.dtStart?.date }) val earliest = dtStarts.minOrNull() // add VTIMEZONE components - usedTimeZones.forEach { - var tz = it.vTimeZone - if (earliest != null) - tz = minifyVTimeZone(tz, earliest) - ical.components += tz - } + for (tz in usedTimeZones) + ical.components += minifyVTimeZone(tz.vTimeZone, earliest) softValidate(ical) CalendarOutputter(false).output(ical, os) diff --git a/src/main/java/at/bitfire/ical4android/ICalendar.kt b/src/main/java/at/bitfire/ical4android/ICalendar.kt index 27f7d55..9cadd7d 100644 --- a/src/main/java/at/bitfire/ical4android/ICalendar.kt +++ b/src/main/java/at/bitfire/ical4android/ICalendar.kt @@ -16,10 +16,7 @@ import net.fortuna.ical4j.model.Parameter import net.fortuna.ical4j.model.Property import net.fortuna.ical4j.model.component.* import net.fortuna.ical4j.model.parameter.Related -import net.fortuna.ical4j.model.property.ProdId -import net.fortuna.ical4j.model.property.RDate -import net.fortuna.ical4j.model.property.RRule -import net.fortuna.ical4j.model.property.TzUrl +import net.fortuna.ical4j.model.property.* import net.fortuna.ical4j.validate.ValidationException import java.io.Reader import java.io.StringReader @@ -119,80 +116,80 @@ open class ICalendar { * Additionally, TZURL properties are filtered. * * @param originalTz time zone definition to minify - * @param start start date for components (usually DTSTART) + * @param start start date for components (usually DTSTART); *null* if unknown * @return minified time zone definition */ - fun minifyVTimeZone(originalTz: VTimeZone, start: Date): VTimeZone { + fun minifyVTimeZone(originalTz: VTimeZone, start: Date?): VTimeZone { val newTz = originalTz.copy() as VTimeZone val keep = mutableSetOf() - // find latest matching STANDARD/DAYLIGHT observances - var latestDaylight: Pair? = null - var latestStandard: Pair? = null - for (observance in newTz.observances) { - val latest = observance.getLatestOnset(start) + if (start != null) { + // find latest matching STANDARD/DAYLIGHT observances + var latestDaylight: Pair? = null + var latestStandard: Pair? = null + for (observance in newTz.observances) { + val latest = observance.getLatestOnset(start) - if (latest == null) // observance begins after "start", keep in any case - keep += observance - else - when (observance) { - is Standard -> - if (latestStandard == null || latest > latestStandard.first) - latestStandard = Pair(latest, observance) - is Daylight -> - if (latestDaylight == null || latest > latestDaylight.first) - latestDaylight = Pair(latest, observance) - } - } + if (latest == null) // observance begins after "start", keep in any case + keep += observance + else + when (observance) { + is Standard -> + if (latestStandard == null || latest > latestStandard.first) + latestStandard = Pair(latest, observance) + is Daylight -> + if (latestDaylight == null || latest > latestDaylight.first) + latestDaylight = Pair(latest, observance) + } + } - // keep latest STANDARD observance - latestStandard?.second?.let { keep += it } - - // Check latest DAYLIGHT for whether it can apply in the future. Otherwise, DST is not - // used in this time zone anymore and the DAYLIGHT component can be dropped completely. - latestDaylight?.second?.let { daylight -> - // check whether start time is in DST - if (latestStandard != null) { - val latestStandardOnset = latestStandard.second.getLatestOnset(start) - val latestDaylightOnset = daylight.getLatestOnset(start) - if (latestStandardOnset != null && latestDaylightOnset != null && latestDaylightOnset > latestStandardOnset) { - // we're currently in DST - keep += daylight - return@let + // keep latest STANDARD observance + latestStandard?.second?.let { keep += it } + + // Check latest DAYLIGHT for whether it can apply in the future. Otherwise, DST is not + // used in this time zone anymore and the DAYLIGHT component can be dropped completely. + latestDaylight?.second?.let { daylight -> + // check whether start time is in DST + if (latestStandard != null) { + val latestStandardOnset = latestStandard.second.getLatestOnset(start) + val latestDaylightOnset = daylight.getLatestOnset(start) + if (latestStandardOnset != null && latestDaylightOnset != null && latestDaylightOnset > latestStandardOnset) { + // we're currently in DST + keep += daylight + return@let + } } - } - // check RRULEs - for (rRule in daylight.getProperties(Property.RRULE)) { - val nextDstOnset = rRule.recur.getNextDate(daylight.startDate.date, start) - if (nextDstOnset != null) { - // there will be a DST onset in the future -> keep DAYLIGHT - keep += daylight - return@let + // check RRULEs + for (rRule in daylight.getProperties(Property.RRULE)) { + val nextDstOnset = rRule.recur.getNextDate(daylight.startDate.date, start) + if (nextDstOnset != null) { + // there will be a DST onset in the future -> keep DAYLIGHT + keep += daylight + return@let + } } - } - // no RRULE, check whether there's an RDATE in the future - for (rDate in daylight.getProperties(Property.RDATE)) { - if (rDate.dates.any { it >= start }) { - // RDATE in the future - keep += daylight - return@let + // no RRULE, check whether there's an RDATE in the future + for (rDate in daylight.getProperties(Property.RDATE)) { + if (rDate.dates.any { it >= start }) { + // RDATE in the future + keep += daylight + return@let + } } } - } - // remove all observances that shall not be kept - val iterator = newTz.observances.iterator() as MutableIterator - while (iterator.hasNext()) { - val entry = iterator.next() - if (!keep.contains(entry)) - iterator.remove() + // remove all observances that shall not be kept + val iterator = newTz.observances.iterator() as MutableIterator + while (iterator.hasNext()) { + val entry = iterator.next() + if (!keep.contains(entry)) + iterator.remove() + } } - // remove TZURL - newTz.properties.filterIsInstance().forEach { - newTz.properties.remove(it) - } + // remove unnecessary properties + newTz.properties.removeAll { it is TzUrl || it is XProperty } // validate minified timezone try { diff --git a/src/main/java/at/bitfire/ical4android/Task.kt b/src/main/java/at/bitfire/ical4android/Task.kt index e236ff5..2ea3afa 100644 --- a/src/main/java/at/bitfire/ical4android/Task.kt +++ b/src/main/java/at/bitfire/ical4android/Task.kt @@ -238,12 +238,8 @@ class Task: ICalendar() { completedAt?.date ).filterNotNull().min() // add VTIMEZONE components - usedTimeZones.forEach { - var tz = it.vTimeZone - if (earliest != null) - tz = minifyVTimeZone(tz, earliest) - ical.components += tz - } + for (tz in usedTimeZones) + ical.components += minifyVTimeZone(tz.vTimeZone, earliest) softValidate(ical) CalendarOutputter(false).output(ical, os) -- GitLab