1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
/***************************************************************************************************
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
**************************************************************************************************/
package at.bitfire.ical4android.validation
import at.bitfire.ical4android.DateUtils
import at.bitfire.ical4android.Event
import at.bitfire.ical4android.Ical4Android
import at.bitfire.ical4android.InvalidCalendarException
import at.bitfire.ical4android.util.TimeApiExtensions.toIcal4jDate
import at.bitfire.ical4android.util.TimeApiExtensions.toIcal4jDateTime
import at.bitfire.ical4android.util.TimeApiExtensions.toLocalDate
import at.bitfire.ical4android.util.TimeApiExtensions.toZoneIdCompat
import net.fortuna.ical4j.model.Date
import net.fortuna.ical4j.model.DateTime
import net.fortuna.ical4j.model.property.DtStart
import net.fortuna.ical4j.model.property.RRule
import java.time.*
/**
* Sometimes CalendarStorage or servers respond with invalid event definitions. Here we try to
* validate, repair and assume whatever seems appropriate before denying the whole event.
*/
class EventValidator(val e: Event) {
fun repair() {
val dtStart = correctStartAndEndTime(e)
sameTypeForDtStartAndRruleUntil(dtStart, e.rRules)
removeRRulesWithUntilBeforeDtStart(dtStart, e.rRules)
}
companion object {
/**
* Ensure proper start and end time
*/
internal fun correctStartAndEndTime(e: Event): DtStart {
val dtStart = e.dtStart ?: throw InvalidCalendarException("Event without start time")
e.dtEnd?.let { dtEnd ->
if (dtStart.date > dtEnd.date) {
Ical4Android.log.warning("DTSTART after DTEND; removing DTEND")
e.dtEnd = null
}
}
return dtStart
}
/**
* Tries to make the value type of UNTIL and DTSTART the same (both DATE or DATETIME).
*/
internal fun sameTypeForDtStartAndRruleUntil(dtStart: DtStart, rRules: MutableList<RRule>) {
if (DateUtils.isDate(dtStart)) {
for (rRule in rRules) {
rRule.recur.until?.let { until ->
if (until is DateTime) {
Ical4Android.log.warning("DTSTART has DATE, but UNTIL has DATETIME; making UNTIL have DATE only")
rRule.recur.until = until.toLocalDate().toIcal4jDate()
}
}
}
} else if (DateUtils.isDateTime(dtStart)) {
for (rRule in rRules) {
rRule.recur.until?.let { until ->
if (until !is DateTime) {
Ical4Android.log.warning("DTSTART has DATETIME, but UNTIL has DATE; copying time from DTSTART to UNTIL")
val timeZone = if (dtStart.timeZone != null)
dtStart.timeZone.toZoneIdCompat()
else if (dtStart.isUtc)
ZoneOffset.UTC
else /* floating time */
ZoneId.systemDefault()
rRule.recur.until =
ZonedDateTime.of(
until.toLocalDate(), // date from until
LocalTime.ofInstant(dtStart.date.toInstant(), timeZone), // time from dtStart
timeZone
).toIcal4jDateTime()
}
}
}
} else
throw InvalidCalendarException("Event with invalid DTSTART value")
}
/**
* Will remove the RRULES of an event where UNTIL lies before DTSTART
*/
internal fun removeRRulesWithUntilBeforeDtStart(dtStart: DtStart, rRules: MutableList<RRule>) {
val iter = rRules.iterator()
while (iter.hasNext()) {
val rRule = iter.next()
// drop invalid RRULEs
if (hasUntilBeforeDtStart(dtStart, rRule))
iter.remove()
}
}
/**
* Checks whether UNTIL of an RRULE lies before DTSTART
*/
internal fun hasUntilBeforeDtStart(dtStart: DtStart, rRule: RRule): Boolean {
val until = rRule.recur.until ?: return false
return until < dtStart.date
}
}
}
|