Commit 9459f80d authored by Ricki Hirner's avatar Ricki Hirner 🐑

Apply ical4j rules to change Outlook timezone IDs to more Android-friendly values

parent ea01dcd5
Pipeline #97962349 passed with stages
in 6 minutes and 26 seconds
buildscript {
ext.versions = [
kotlin: '1.3.50',
kotlin: '1.3.60',
dokka: '0.10.0',
ical4j: '2.2.6'
]
......
......@@ -21,6 +21,7 @@ import java.io.IOException
import java.io.OutputStream
import java.io.Reader
import java.util.*
import java.util.logging.Level
class Event: ICalendar() {
......@@ -82,6 +83,12 @@ class Event: ICalendar() {
throw InvalidCalendarException("iCalendar object contains invalid value", e)
}
try {
ICalPreprocessor.preProcess(ical)
} catch (e: Exception) {
Constants.log.log(Level.WARNING, "Couldn't pre-process iCalendar", e)
}
// fill calendar properties
properties?.let {
ical.getProperty(CALENDAR_NAME)?.let { calName ->
......
package at.bitfire.ical4android
import net.fortuna.ical4j.model.Calendar
import net.fortuna.ical4j.model.Property
import net.fortuna.ical4j.transform.rfc5545.DateListPropertyRule
import net.fortuna.ical4j.transform.rfc5545.DatePropertyRule
import net.fortuna.ical4j.transform.rfc5545.Rfc5545PropertyRule
import java.util.logging.Level
/**
* Applies some rules to increase compatibility or parsed iCalendars:
*
* - [DatePropertyRule], [DateListPropertyRule]: to rename Outlook-specific TZID parameters
* (like "W. Europe Standard Time" to an Android-friendly name like "Europe/Vienna")
*
*/
object ICalPreprocessor {
private val propertyRules = arrayOf(
DatePropertyRule(),
DateListPropertyRule()
)
/**
* Applies the set of rules (see class definition) to a given calendar object.
*
* @param calendar the calendar object that is going to be modified
*/
fun preProcess(calendar: Calendar) {
for (component in calendar.components) {
for (property in component.properties)
applyRules(property)
}
}
@Suppress("UNCHECKED_CAST")
private fun applyRules(property: Property) {
propertyRules
.filter { rule -> rule.supportedType.isAssignableFrom(property::class.java) }
.forEach {
Constants.log.log(Level.INFO, "Applying rules to ${property.toString()}")
(it as Rfc5545PropertyRule<Property>).applyTo(property)
Constants.log.log(Level.INFO, "-> ${property.toString()}")
}
}
}
\ No newline at end of file
......@@ -30,7 +30,7 @@ object MiscUtils {
val tzID = tz.id ?: return
val deviceTzID = DateUtils.findAndroidTimezoneID(tzID)
if (tzID != deviceTzID) {
Constants.log.warning("Android doesn't know time zone \"$tzID\", storing event in time zone \"$deviceTzID\"")
Constants.log.warning("Android doesn't know time zone \"$tzID\", assuming device time zone \"$deviceTzID\"")
date.timeZone = DateUtils.tzRegistry.getTimeZone(deviceTzID)
}
}
......
......@@ -102,9 +102,9 @@ class EventTest {
// event with start+end date-time
val eViennaEvolution = parseCalendar("vienna-evolution.ics").first()
assertEquals(1381330800000L, eViennaEvolution.dtStart!!.date.time)
assertEquals("/freeassociation.sourceforge.net/Tzfile/Europe/Vienna", eViennaEvolution.dtStart!!.timeZone.id)
assertEquals("Europe/Vienna", eViennaEvolution.dtStart!!.timeZone.id)
assertEquals(1381334400000L, eViennaEvolution.dtEnd!!.date.time)
assertEquals("/freeassociation.sourceforge.net/Tzfile/Europe/Vienna", eViennaEvolution.dtEnd!!.timeZone.id)
assertEquals("Europe/Vienna", eViennaEvolution.dtEnd!!.timeZone.id)
}
@Test
......
package at.bitfire.ical4android
import net.fortuna.ical4j.data.CalendarBuilder
import net.fortuna.ical4j.model.Component
import net.fortuna.ical4j.model.component.VEvent
import org.junit.Assert.assertEquals
import org.junit.Test
import java.io.InputStreamReader
class ICalPreprocessorTest {
@Test
fun testMsTimeZones() {
javaClass.classLoader!!.getResourceAsStream("events/outlook1.ics").use { stream ->
val reader = InputStreamReader(stream, Charsets.UTF_8)
val calendar = CalendarBuilder().build(reader)
val vEvent = calendar.getComponent(Component.VEVENT) as VEvent
assertEquals("W. Europe Standard Time", vEvent.startDate.timeZone.id)
ICalPreprocessor.preProcess(calendar)
assertEquals("Europe/Vienna", vEvent.startDate.timeZone.id)
}
}
}
\ No newline at end of file
BEGIN:VCALENDAR
METHOD:PUBLISH
PRODID:Microsoft Exchange Server 2010
VERSION:2.0
X-WR-CALNAME:Calendar
BEGIN:VTIMEZONE
TZID:China Standard Time
BEGIN:STANDARD
DTSTART:16010101T000000
TZOFFSETFROM:+0800
TZOFFSETTO:+0800
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:16010101T000000
TZOFFSETFROM:+0800
TZOFFSETTO:+0800
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VTIMEZONE
TZID:W. Europe Standard Time
BEGIN:STANDARD
DTSTART:16010101T030000
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:16010101T020000
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VTIMEZONE
TZID:India Standard Time
BEGIN:STANDARD
DTSTART:16010101T000000
TZOFFSETFROM:+0530
TZOFFSETTO:+0530
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:16010101T000000
TZOFFSETFROM:+0530
TZOFFSETTO:+0530
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
DESCRIPTION:\n.............................................................
.................................\n\n\n
RRULE:FREQ=WEEKLY;UNTIL=20191218T080000Z;INTERVAL=1;BYDAY=WE;WKST=MO
UID:040000008200E00074C5B7101A82E00800000000907682BE2B88D501000000000000000
01000000077F6CDA3B634104B9BC1ED539119F558
SUMMARY:Weekly Meeting - Test
DTSTART;TZID=W. Europe Standard Time:20191023T090000
DTEND;TZID=W. Europe Standard Time:20191023T093000
CLASS:PUBLIC
PRIORITY:5
DTSTAMP:20191119T090349Z
TRANSP:OPAQUE
STATUS:CONFIRMED
SEQUENCE:1
LOCATION:Skype-Besprechung
X-MICROSOFT-CDO-APPT-SEQUENCE:1
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
X-MICROSOFT-CDO-ALLDAYEVENT:FALSE
X-MICROSOFT-CDO-IMPORTANCE:1
X-MICROSOFT-CDO-INSTTYPE:1
X-MICROSOFT-DONOTFORWARDMEETING:FALSE
X-MICROSOFT-DISALLOW-COUNTER:FALSE
END:VEVENT
END:VCALENDAR
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment