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)); }