Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 0 additions & 109 deletions lib/src/main/kotlin/at/bitfire/ical4android/ICalendar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,16 @@ import at.bitfire.synctools.BuildConfig
import at.bitfire.synctools.exception.InvalidICalendarException
import at.bitfire.synctools.icalendar.ICalendarParser
import at.bitfire.synctools.icalendar.validation.ICalPreprocessor
import at.bitfire.synctools.util.AndroidTimeUtils.toInstant
import net.fortuna.ical4j.data.CalendarBuilder
import net.fortuna.ical4j.data.ParserException
import net.fortuna.ical4j.model.Calendar
import net.fortuna.ical4j.model.Parameter
import net.fortuna.ical4j.model.Property
import net.fortuna.ical4j.model.component.VAlarm
import net.fortuna.ical4j.model.component.VTimeZone
import net.fortuna.ical4j.model.parameter.Related
import net.fortuna.ical4j.model.property.Color
import net.fortuna.ical4j.model.property.DateProperty
import net.fortuna.ical4j.model.property.DtStart
import net.fortuna.ical4j.model.property.ProdId
import net.fortuna.ical4j.model.property.Trigger
import net.fortuna.ical4j.validate.ValidationException
import java.io.Reader
import java.io.StringReader
import java.time.Duration
import java.time.Instant
import java.time.Period
import java.time.temporal.Temporal
import java.util.LinkedList
import java.util.UUID
import java.util.logging.Level
Expand Down Expand Up @@ -156,104 +145,6 @@ open class ICalendar {
}
}


// misc. iCalendar helpers

/**
* Calculates the minutes before/after an event/task to know when a given alarm occurs.
*
* @param alarm the alarm to calculate the minutes from
* @param refStart reference `DTSTART` from the calendar component
* @param refEnd reference `DTEND` (`VEVENT`) or `DUE` (`VTODO`) from the calendar component
* @param allowRelEnd *true*: caller accepts minutes related to the end;
* *false*: caller only accepts minutes related to the start
*
* Android's alarm granularity is minutes. This methods calculates with milliseconds, but the result
* is rounded down to minutes (seconds cut off).
*
* @return Pair of values:
*
* 1. whether the minutes are related to the start or end (always [Related.START] if [allowRelEnd] is *false*)
* 2. number of minutes before start/end (negative value means number of minutes *after* start/end)
*
* May be *null* if there's not enough information to calculate the number of minutes.
*/
fun vAlarmToMin(
alarm: VAlarm,
refStart: DtStart<*>?,
refEnd: DateProperty<*>?,
refDuration: net.fortuna.ical4j.model.property.Duration?,
allowRelEnd: Boolean
): Pair<Related, Int>? {
val trigger = alarm.getProperty<Trigger>(Property.TRIGGER).getOrNull() ?: return null

// Note: big method – maybe split?

val minutes: Int // minutes before/after the event
var related: Related = trigger.getParameter<Related>(Parameter.RELATED).getOrNull() ?: Related.START

// event/task start/end time
val start = refStart?.date?.toInstant()
var end = refEnd?.date?.toInstant()

// event/task end time
if (end == null && start != null)
end = when (val refDur = refDuration?.duration) {
is Duration -> start + refDur
is Period -> start + Duration.between(start, start + refDur)
else -> null
}

// event/task duration
val duration: Duration? =
if (start != null && end != null)
Duration.between(start, end)
else
null

val triggerDur = trigger.duration
val triggerTime = trigger.date

if (triggerDur != null) {
// TRIGGER value is a DURATION. Important:
// 1) Negative values in TRIGGER mean positive values in Reminders.MINUTES and vice versa.
// 2) Android doesn't know alarm seconds, but only minutes. Cut off seconds from the final result.
// 3) DURATION can be a Duration (time-based) or a Period (date-based), which have to be treated differently.
var millisBefore =
when (triggerDur) {
is Duration -> -triggerDur.toMillis()
is Period -> {
// TODO: Take time zones into account (will probably be possible with ical4j 4.x).
// For instance, an alarm one day before the DST change should be 23/25 hours before the event.
-Duration.ofDays(triggerDur.days.toLong()).toMillis() // months and years are not used in DURATION values; weeks are calculated to days
}
else -> throw AssertionError("triggerDur must be Duration or Period")
}

if (related == Related.END && !allowRelEnd) {
if (duration == null) {
logger.warning("Event/task without duration; can't calculate END-related alarm")
return null
}
// move alarm towards end
related = Related.START
millisBefore -= duration.toMillis()
}
minutes = (millisBefore / 60000).toInt()

} else if (triggerTime != null && start != null) {
// TRIGGER value is a DATE-TIME, calculate minutes from start time
related = Related.START
minutes = Duration.between(triggerTime, start).toMinutes().toInt()

} else {
logger.log(Level.WARNING, "VALARM TRIGGER type is not DURATION or DATE-TIME (requires event DTSTART for Android), ignoring alarm", alarm)
return null
}

return Pair(related, minutes)
}

}


Expand Down
26 changes: 26 additions & 0 deletions lib/src/main/kotlin/at/bitfire/ical4android/Task.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package at.bitfire.ical4android

import androidx.annotation.IntRange
import at.bitfire.ical4android.util.DateUtils
import at.bitfire.synctools.util.AndroidTimeUtils.toInstant
import net.fortuna.ical4j.model.Property
import net.fortuna.ical4j.model.component.VAlarm
import net.fortuna.ical4j.model.property.Clazz
Expand All @@ -23,6 +24,7 @@ import net.fortuna.ical4j.model.property.RDate
import net.fortuna.ical4j.model.property.RRule
import net.fortuna.ical4j.model.property.RelatedTo
import net.fortuna.ical4j.model.property.Status
import java.time.Instant
import java.util.LinkedList

/**
Expand Down Expand Up @@ -75,4 +77,28 @@ data class Task(
?: true
}

/**
* The "end date" of this task.
*
* Returns…
* - [due] if present, otherwise…
* - [Due] instance containing the end date as [Instant] calculated from [dtStart] and
* [duration] if both present, otherwise…
* - `null`.
*/
val end: Due<*>?
get() {
if (due != null) {
return due
}

val start = dtStart?.date?.toInstant()
val duration = duration?.duration
if (start != null && duration != null) {
val end = start + duration
return Due(end)
}

return null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,13 @@ import android.content.ContentValues
import android.content.Entity
import android.provider.CalendarContract.Reminders
import androidx.core.content.contentValuesOf
import at.bitfire.ical4android.ICalendar
import at.bitfire.synctools.icalendar.dtStart
import at.bitfire.synctools.util.AlarmTriggerCalculator
import net.fortuna.ical4j.model.Property
import net.fortuna.ical4j.model.component.VAlarm
import net.fortuna.ical4j.model.component.VEvent
import net.fortuna.ical4j.model.property.Action
import net.fortuna.ical4j.model.property.DtEnd
import java.time.temporal.Temporal
import java.util.Locale
import kotlin.jvm.optionals.getOrNull

class RemindersBuilder: AndroidEntityBuilder {
Expand All @@ -39,11 +37,10 @@ class RemindersBuilder: AndroidEntityBuilder {
else -> Reminders.METHOD_DEFAULT // won't trigger an alarm on the Android device
}

val minutes = ICalendar.vAlarmToMin(
val minutes = AlarmTriggerCalculator.alarmTriggerToMinutes(
alarm = alarm,
refStart = event.dtStart<Temporal>(),
refEnd = event.getEndDate<Temporal>().getOrNull(),
refDuration = event.duration,
refEnd = event.getEndDate<Temporal>(true).getOrNull(),
allowRelEnd = false
)?.second ?: Reminders.MINUTES_DEFAULT

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ package at.bitfire.synctools.mapping.tasks

import android.content.ContentValues
import android.content.Entity
import at.bitfire.ical4android.ICalendar
import at.bitfire.ical4android.Task
import at.bitfire.ical4android.UnknownProperty
import at.bitfire.synctools.icalendar.DatePropertyTzMapper.normalizedDate
Expand All @@ -20,6 +19,7 @@ import at.bitfire.synctools.storage.tasks.DmfsTask.Companion.COLUMN_FLAGS
import at.bitfire.synctools.storage.tasks.DmfsTask.Companion.UNKNOWN_PROPERTY_DATA
import at.bitfire.synctools.storage.tasks.DmfsTaskList
import at.bitfire.synctools.storage.tasks.TasksBatchOperation
import at.bitfire.synctools.util.AlarmTriggerCalculator
import at.bitfire.synctools.util.AndroidTimeUtils
import at.bitfire.synctools.util.AndroidTimeUtils.toTimestamp
import net.fortuna.ical4j.model.Parameter
Expand Down Expand Up @@ -93,7 +93,7 @@ class DmfsTaskBuilder(
builder .withValue(Tasks.LIST_ID, taskList.id)

// new builders

val entity = Entity(ContentValues())
for (fieldBuilder in fieldBuilders)
fieldBuilder.build(task, entity)
Expand Down Expand Up @@ -224,11 +224,10 @@ class DmfsTaskBuilder(

private fun insertAlarms(batch: TasksBatchOperation, idxTask: Int?) {
for (alarm in task.alarms) {
val (alarmRef, minutes) = ICalendar.vAlarmToMin(
val (alarmRef, minutes) = AlarmTriggerCalculator.alarmTriggerToMinutes(
alarm = alarm,
refStart = task.dtStart,
refEnd = task.due,
refDuration = task.duration,
refEnd = task.end,
allowRelEnd = true
) ?: continue
val ref = when (alarmRef) {
Expand Down
Loading
Loading