diff --git a/opentasks-provider/src/androidTest/java/org/dmfs/provider/tasks/TaskProviderRecurrenceTest.java b/opentasks-provider/src/androidTest/java/org/dmfs/provider/tasks/TaskProviderRecurrenceTest.java
index ffe5f29af..69123f627 100644
--- a/opentasks-provider/src/androidTest/java/org/dmfs/provider/tasks/TaskProviderRecurrenceTest.java
+++ b/opentasks-provider/src/androidTest/java/org/dmfs/provider/tasks/TaskProviderRecurrenceTest.java
@@ -200,6 +200,69 @@ public void testRRule() throws InvalidRecurrenceRuleException
}
+ /**
+ * Test if instances of a task with a timed DTSTART, DUE and a floating RRULE UNTIL.
+ *
+ * Note, this combination should not be accepted by the provider. For the time being, however, it should be tolerated instead of causing a crash.
+ */
+ @Test
+ public void testRRuleWithFloatingMismatch() throws InvalidRecurrenceRuleException
+ {
+ RowSnapshot taskList = new VirtualRowSnapshot<>(new LocalTaskListsTable(mAuthority));
+ Table instancesTable = new InstanceTable(mAuthority);
+ RowSnapshot task = new VirtualRowSnapshot<>(new TaskListScoped(taskList, new TasksTable(mAuthority)));
+
+ Duration hour = new Duration(1, 0, 3600 /* 1 hour */);
+ DateTime start = DateTime.parse("20180104T123456Z");
+ DateTime due = start.addDuration(hour);
+ DateTime localStart = start.shiftTimeZone(TimeZone.getDefault());
+
+ Duration day = new Duration(1, 1, 0);
+
+ DateTime second = localStart.addDuration(day);
+ DateTime third = second.addDuration(day);
+ DateTime fourth = third.addDuration(day);
+ DateTime fifth = fourth.addDuration(day);
+
+ DateTime localDue = due.shiftTimeZone(TimeZone.getDefault());
+
+ assertThat(new Seq<>(
+ new Put<>(taskList, new EmptyRowData<>()),
+ new Put<>(task,
+ new Composite<>(
+ new TimeData<>(start, due),
+ new RRuleTaskData(new RecurrenceRule("FREQ=DAILY;UNTIL=20180106", RecurrenceRule.RfcMode.RFC2445_LAX))))
+
+ ), resultsIn(mClient,
+ new Assert<>(task,
+ new Composite<>(
+ new TimeData<>(start, due),
+ new CharSequenceRowData<>(Tasks.RRULE, "FREQ=DAILY;UNTIL=20180106"))),
+// new Counted<>(5, new AssertRelated<>(instancesTable, Instances.TASK_ID, task)),
+ new Counted<>(1, new AssertRelated<>(instancesTable, Instances.TASK_ID, task)),
+ // 1st instance:
+ new AssertRelated<>(instancesTable, Instances.TASK_ID, task,
+ new InstanceTestData(localStart, localDue, new Present<>(start), 0),
+ new EqArg(Instances.INSTANCE_ORIGINAL_TIME, start.getTimestamp()))/*,
+ // 2nd instance:
+ new AssertRelated<>(instancesTable, Instances.TASK_ID, task,
+ new InstanceTestData(second, second.addDuration(hour), new Present<>(second), 1),
+ new EqArg(Instances.INSTANCE_ORIGINAL_TIME, second.getTimestamp())),
+ // 3rd instance:
+ new AssertRelated<>(instancesTable, Instances.TASK_ID, task,
+ new InstanceTestData(third, third.addDuration(hour), new Present<>(third), 2),
+ new EqArg(Instances.INSTANCE_ORIGINAL_TIME, third.getTimestamp())),
+ // 4th instance:
+ new AssertRelated<>(instancesTable, Instances.TASK_ID, task,
+ new InstanceTestData(fourth, fourth.addDuration(hour), new Present<>(fourth), 3),
+ new EqArg(Instances.INSTANCE_ORIGINAL_TIME, fourth.getTimestamp())),
+ // 5th instance:
+ new AssertRelated<>(instancesTable, Instances.TASK_ID, task,
+ new InstanceTestData(fifth, fifth.addDuration(hour), new Present<>(fifth), 4),
+ new EqArg(Instances.INSTANCE_ORIGINAL_TIME, fifth.getTimestamp())) */)
+ );
+ }
+
/**
* Test if instances of a task with an all-day DTSTART, DUE and an RRULE.
@@ -263,6 +326,69 @@ public void testAllDayRRule() throws InvalidRecurrenceRuleException
}
+ /**
+ * Test if instances of a task with an all-day DTSTART, DUE and an RRULE with an absolute UNTIL
+ *
+ * Note, this combination should not be accepted by the provider. For the time being, however, it should be tolerated instead of causing a crash.
+ */
+ @Test
+ public void testAllDayRRuleFloatingMismatch() throws InvalidRecurrenceRuleException
+ {
+ RowSnapshot taskList = new VirtualRowSnapshot<>(new LocalTaskListsTable(mAuthority));
+ Table instancesTable = new InstanceTable(mAuthority);
+ RowSnapshot task = new VirtualRowSnapshot<>(new TaskListScoped(taskList, new TasksTable(mAuthority)));
+
+ Duration days = new Duration(1, 2, 0);
+ DateTime start = DateTime.parse("20180104");
+ DateTime due = start.addDuration(days);
+ DateTime localStart = start;
+
+ Duration day = new Duration(1, 1, 0);
+
+ DateTime second = localStart.addDuration(day);
+ DateTime third = second.addDuration(day);
+ DateTime fourth = third.addDuration(day);
+ DateTime fifth = fourth.addDuration(day);
+
+ DateTime localDue = due;
+
+ assertThat(new Seq<>(
+ new Put<>(taskList, new EmptyRowData<>()),
+ new Put<>(task,
+ new Composite<>(
+ new TimeData<>(start, due),
+ new RRuleTaskData(new RecurrenceRule("FREQ=DAILY;UNTIL=20180106T120000Z", RecurrenceRule.RfcMode.RFC2445_LAX))))
+
+ ), resultsIn(mClient,
+ new Assert<>(task,
+ new Composite<>(
+ new TimeData<>(start, due),
+ new CharSequenceRowData<>(Tasks.RRULE, "FREQ=DAILY;UNTIL=20180106T120000Z"))),
+// new Counted<>(5, new AssertRelated<>(instancesTable, Instances.TASK_ID, task)),
+ new Counted<>(1, new AssertRelated<>(instancesTable, Instances.TASK_ID, task)),
+ // 1st instance:
+ new AssertRelated<>(instancesTable, Instances.TASK_ID, task,
+ new InstanceTestData(localStart, localDue, new Present<>(start), 0),
+ new EqArg(Instances.INSTANCE_ORIGINAL_TIME, start.getTimestamp()))/*,
+ // 2nd instance:
+ new AssertRelated<>(instancesTable, Instances.TASK_ID, task,
+ new InstanceTestData(second, second.addDuration(hour), new Present<>(second), 1),
+ new EqArg(Instances.INSTANCE_ORIGINAL_TIME, second.getTimestamp())),
+ // 3rd instance:
+ new AssertRelated<>(instancesTable, Instances.TASK_ID, task,
+ new InstanceTestData(third, third.addDuration(hour), new Present<>(third), 2),
+ new EqArg(Instances.INSTANCE_ORIGINAL_TIME, third.getTimestamp())),
+ // 4th instance:
+ new AssertRelated<>(instancesTable, Instances.TASK_ID, task,
+ new InstanceTestData(fourth, fourth.addDuration(hour), new Present<>(fourth), 3),
+ new EqArg(Instances.INSTANCE_ORIGINAL_TIME, fourth.getTimestamp())),
+ // 5th instance:
+ new AssertRelated<>(instancesTable, Instances.TASK_ID, task,
+ new InstanceTestData(fifth, fifth.addDuration(hour), new Present<>(fifth), 4),
+ new EqArg(Instances.INSTANCE_ORIGINAL_TIME, fifth.getTimestamp())) */)
+ );
+ }
+
/**
* Test if instances of a task with a DUE and an RRULE but no DTSTART.
diff --git a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/utils/TaskInstanceIterable.java b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/utils/TaskInstanceIterable.java
index 23c3a63b7..24cb9e3c6 100644
--- a/opentasks-provider/src/main/java/org/dmfs/provider/tasks/utils/TaskInstanceIterable.java
+++ b/opentasks-provider/src/main/java/org/dmfs/provider/tasks/utils/TaskInstanceIterable.java
@@ -27,6 +27,7 @@
import org.dmfs.rfc5545.recurrenceset.RecurrenceSetIterator;
import java.util.Iterator;
+import java.util.TimeZone;
/**
@@ -54,6 +55,21 @@ public Iterator iterator()
RecurrenceRule rule = mTaskAdapter.valueOf(TaskAdapter.RRULE);
if (rule != null)
{
+ if (rule.getUntil() != null && dtstart.isFloating() != rule.getUntil().isFloating())
+ {
+ // rule UNTIL date mismatches start. This is merely a workaround for existing users. In future we should make sure
+ // such tasks don't exist
+ if (dtstart.isFloating())
+ {
+ // make until floating too by making it floating in the current time zone
+ rule.setUntil(rule.getUntil().shiftTimeZone(TimeZone.getDefault()).swapTimeZone(null));
+ }
+ else
+ {
+ // anchor UNTIL in the current time zone
+ rule.setUntil(new DateTime(null, rule.getUntil().getTimestamp()).swapTimeZone(TimeZone.getDefault()));
+ }
+ }
set.addInstances(new RecurrenceRuleAdapter(rule));
}