Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
What's new
7
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Switch to GitLab Next
Sign in / Register
Toggle navigation
Open sidebar
bitfire web engineering
ical4android
Commits
605b9045
Commit
605b9045
authored
Jan 01, 2020
by
Ricki Hirner
🐑
Browse files
Event: improved local handling of exceptions of recurring events
parent
c1442429
Pipeline
#106491270
passed with stages
in 5 minutes and 32 seconds
Changes
6
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
85 additions
and
24 deletions
+85
-24
src/main/java/at/bitfire/ical4android/AndroidEvent.kt
src/main/java/at/bitfire/ical4android/AndroidEvent.kt
+1
-3
src/main/java/at/bitfire/ical4android/DateUtils.kt
src/main/java/at/bitfire/ical4android/DateUtils.kt
+9
-0
src/main/java/at/bitfire/ical4android/Event.kt
src/main/java/at/bitfire/ical4android/Event.kt
+30
-16
src/main/java/at/bitfire/ical4android/ICalendar.kt
src/main/java/at/bitfire/ical4android/ICalendar.kt
+0
-2
src/main/java/at/bitfire/ical4android/MiscUtils.kt
src/main/java/at/bitfire/ical4android/MiscUtils.kt
+3
-3
src/test/java/at/bitfire/ical4android/EventTest.kt
src/test/java/at/bitfire/ical4android/EventTest.kt
+42
-0
No files found.
src/main/java/at/bitfire/ical4android/AndroidEvent.kt
View file @
605b9045
...
...
@@ -254,9 +254,7 @@ abstract class AndroidEvent(
// exceptions from recurring events
row
.
getAsLong
(
Events
.
ORIGINAL_INSTANCE_TIME
)
?.
let
{
originalInstanceTime
->
var
originalAllDay
=
false
row
.
getAsInteger
(
Events
.
ORIGINAL_ALL_DAY
)
?.
let
{
originalAllDay
=
it
!=
0
}
val
originalAllDay
=
(
row
.
getAsInteger
(
Events
.
ORIGINAL_ALL_DAY
)
?:
0
)
!=
0
val
originalDate
=
if
(
originalAllDay
)
Date
(
originalInstanceTime
)
else
DateTime
(
originalInstanceTime
)
...
...
src/main/java/at/bitfire/ical4android/DateUtils.kt
View file @
605b9045
...
...
@@ -15,6 +15,7 @@ import net.fortuna.ical4j.model.TimeZone
import
net.fortuna.ical4j.model.component.VTimeZone
import
net.fortuna.ical4j.model.parameter.Value
import
net.fortuna.ical4j.model.property.DateListProperty
import
net.fortuna.ical4j.model.property.DateProperty
import
net.fortuna.ical4j.model.property.ExDate
import
net.fortuna.ical4j.model.property.RDate
import
java.io.StringReader
...
...
@@ -74,6 +75,14 @@ object DateUtils {
return
deviceTZ
}
/**
* Determines whether a given date represents a DATE-TIME value.
* @param date date property to check
* @return *true* if the date is a DATE-TIME value; *false* otherwise (for instance, when the
* date is a DATE value or null)
*/
fun
isDateTime
(
date
:
DateProperty
?)
=
date
!=
null
&&
date
.
date
is
DateTime
/**
* Parses a VTIMEZONE definition to a VTimeZone object.
* @param timezoneDef VTIMEZONE definition
...
...
src/main/java/at/bitfire/ical4android/Event.kt
View file @
605b9045
...
...
@@ -8,6 +8,7 @@
package
at.bitfire.ical4android
import
at.bitfire.ical4android.DateUtils.isDateTime
import
at.bitfire.ical4android.ICalendar.Companion.CALENDAR_NAME
import
net.fortuna.ical4j.data.CalendarOutputter
import
net.fortuna.ical4j.data.ParserException
...
...
@@ -206,24 +207,37 @@ class Event: ICalendar() {
// recurrence exceptions
for
(
exception
in
exceptions
)
{
// make sure that
// - exceptions have the same UID as the main event and
// - RECURRENCE-IDs have the same timezone as the main event's DTSTART
// exceptions must always have the same UID as the main event
exception
.
uid
=
uid
exception
.
recurrenceId
?.
let
{
recurrenceId
->
if
(
recurrenceId
.
timeZone
!=
dtStart
.
timeZone
)
{
recurrenceId
.
timeZone
=
dtStart
.
timeZone
exception
.
recurrenceId
=
recurrenceId
}
// create VEVENT for exception
val
vException
=
exception
.
toVEvent
()
components
+=
vException
val
recurrenceId
=
exception
.
recurrenceId
if
(
recurrenceId
==
null
)
{
Constants
.
log
.
warning
(
"Ignoring exception without recurrenceId"
)
continue
}
/* Exceptions must always have the same value type as DTSTART [RFC 5545 3.8.4.4].
If this is not the case, we don't add the exception to the event because we're
strict in what we send (and servers may reject such a case).
*/
if
(
isDateTime
(
recurrenceId
)
!=
isDateTime
(
dtStart
))
{
Constants
.
log
.
warning
(
"Ignoring exception $recurrenceId with other date type than dtStart: $dtStart"
)
continue
}
// remember used time zones
exception
.
dtStart
?.
timeZone
?.
let
(
usedTimeZones
::
add
)
exception
.
dtEnd
?.
timeZone
?.
let
(
usedTimeZones
::
add
)
// for simplicity and compatibility, rewrite date-time exceptions to the same time zone as DTSTART
if
(
isDateTime
(
recurrenceId
)
&&
recurrenceId
.
timeZone
!=
dtStart
.
timeZone
)
{
Constants
.
log
.
fine
(
"Changing timezone of $recurrenceId to same time zone as dtStart: $dtStart"
)
recurrenceId
.
timeZone
=
dtStart
.
timeZone
}
// create and add VEVENT for exception
val
vException
=
exception
.
toVEvent
()
components
+=
vException
// remember used time zones
exception
.
dtStart
?.
timeZone
?.
let
(
usedTimeZones
::
add
)
exception
.
dtEnd
?.
timeZone
?.
let
(
usedTimeZones
::
add
)
}
// add VTIMEZONE components
...
...
@@ -243,7 +257,7 @@ class Event: ICalendar() {
* @return generated VEvent
*/
private
fun
toVEvent
():
VEvent
{
val
event
=
VEvent
(
true
/* generates DTSTAMP */
)
val
event
=
VEvent
(
/* generates DTSTAMP */
)
val
props
=
event
.
properties
props
+=
Uid
(
uid
)
...
...
@@ -258,7 +272,7 @@ class Event: ICalendar() {
description
?.
let
{
props
+=
Description
(
it
)
}
color
?.
let
{
props
+=
Color
(
null
,
it
.
name
)
}
props
+=
dtStart
dtStart
?.
let
{
props
+=
it
}
dtEnd
?.
let
{
props
+=
it
}
duration
?.
let
{
props
+=
it
}
...
...
src/main/java/at/bitfire/ical4android/ICalendar.kt
View file @
605b9045
...
...
@@ -99,8 +99,6 @@ open class ICalendar {
// time zone helpers
fun
isDateTime
(
date
:
DateProperty
?)
=
date
!=
null
&&
date
.
date
is
DateTime
/**
* Minifies a VTIMEZONE so that only components after [start] are kept.
* Doesn't return the smallest possible VTIMEZONE at the moment, but
...
...
src/main/java/at/bitfire/ical4android/MiscUtils.kt
View file @
605b9045
...
...
@@ -11,7 +11,7 @@ package at.bitfire.ical4android
import
android.content.ContentValues
import
android.database.Cursor
import
android.database.DatabaseUtils
import
net.fortuna.ical4j.model.TextList
import
at.bitfire.ical4android.DateUtils.isDateTime
import
net.fortuna.ical4j.model.property.DateProperty
import
net.fortuna.ical4j.util.TimeZones
import
java.lang.reflect.Modifier
...
...
@@ -25,7 +25,7 @@ object MiscUtils {
* @param date DateProperty to validate. Values which are not DATE-TIME will be ignored.
*/
fun
androidifyTimeZone
(
date
:
DateProperty
?)
{
if
(
ICalendar
.
isDateTime
(
date
))
{
if
(
isDateTime
(
date
))
{
val
tz
=
date
!!
.
timeZone
?:
return
val
tzID
=
tz
.
id
?:
return
val
deviceTzID
=
DateUtils
.
findAndroidTimezoneID
(
tzID
)
...
...
@@ -47,7 +47,7 @@ object MiscUtils {
* - the currently set default time zone ID for floating date-times
*/
fun
getTzId
(
date
:
DateProperty
):
String
=
if
(
ICalendar
.
isDateTime
(
date
))
{
if
(
isDateTime
(
date
))
{
when
{
date
.
isUtc
->
// DATE-TIME in UTC format
...
...
src/test/java/at/bitfire/ical4android/EventTest.kt
View file @
605b9045
...
...
@@ -7,10 +7,14 @@
*/
package
at.bitfire.ical4android
import
net.fortuna.ical4j.model.Date
import
net.fortuna.ical4j.model.DateTime
import
net.fortuna.ical4j.model.Dur
import
net.fortuna.ical4j.model.TimeZoneRegistryFactory
import
net.fortuna.ical4j.model.component.VAlarm
import
net.fortuna.ical4j.model.property.DtStart
import
net.fortuna.ical4j.model.property.RRule
import
net.fortuna.ical4j.model.property.RecurrenceId
import
org.junit.Assert.*
import
org.junit.Test
import
java.io.ByteArrayOutputStream
...
...
@@ -91,6 +95,44 @@ class EventTest {
assertEquals
(
"Unknown Value"
,
unknown
.
value
)
}
@Test
fun
testRecurringWriteFullDayException
()
{
val
event
=
Event
().
apply
{
uid
=
"test1"
dtStart
=
DtStart
(
"20190117T083000"
,
DateUtils
.
tzRegistry
.
getTimeZone
(
"Europe/Berlin"
))
summary
=
"Main event"
rRule
=
RRule
(
"FREQ=DAILY;COUNT=5"
)
exceptions
+=
arrayOf
(
Event
().
apply
{
uid
=
"test2"
recurrenceId
=
RecurrenceId
(
DateTime
(
"20190118T073000"
,
DateUtils
.
tzRegistry
.
getTimeZone
(
"Europe/London"
)))
summary
=
"Normal exception"
},
Event
().
apply
{
uid
=
"test3"
recurrenceId
=
RecurrenceId
(
Date
(
"20190223"
))
summary
=
"Full-day exception"
}
)
}
val
baos
=
ByteArrayOutputStream
()
event
.
write
(
baos
)
val
iCal
=
baos
.
toString
()
assertTrue
(
iCal
.
contains
(
"UID:test1\r\n"
))
assertTrue
(
iCal
.
contains
(
"DTSTART;TZID=Europe/Berlin:20190117T083000\r\n"
))
// first RECURRENCE-ID has been rewritten
// - to main event's UID
// - to time zone Europe/Berlin (with one hour time difference)
assertTrue
(
iCal
.
contains
(
"UID:test1\r\n"
+
"RECURRENCE-ID;TZID=Europe/Berlin:20190118T083000\r\n"
+
"SUMMARY:Normal exception\r\n"
+
"END:VEVENT"
))
// no RECURRENCE-ID;VALUE=DATE:20190223
assertFalse
(
iCal
.
contains
(
":20190223"
))
}
@Test
fun
testRecurringWithException
()
{
val
event
=
parseCalendar
(
"recurring-with-exception1.ics"
).
first
()
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment