Rewrote repeats completely to fulfil new requirements

pull/14/head
Tim Su 15 years ago
parent 6e57942bdc
commit 31c2355742

@ -54,7 +54,6 @@ public class RepeatTaskCompleteListener extends BroadcastReceiver {
return; return;
} }
System.err.println("REPEATING, time to clonear");
StatisticsService.reportEvent("v2-task-repeat"); //$NON-NLS-1$ StatisticsService.reportEvent("v2-task-repeat"); //$NON-NLS-1$
long hideUntil = task.getValue(Task.HIDE_UNTIL); long hideUntil = task.getValue(Task.HIDE_UNTIL);
@ -87,96 +86,116 @@ public class RepeatTaskCompleteListener extends BroadcastReceiver {
} }
} }
/** Compute next due date */
public static long computeNextDueDate(Task task, String recurrence) throws ParseException { public static long computeNextDueDate(Task task, String recurrence) throws ParseException {
DateValue repeatFrom;
Date repeatFromDate = new Date();
DateValue today = new DateValueImpl(repeatFromDate.getYear() + 1900,
repeatFromDate.getMonth() + 1, repeatFromDate.getDate());
boolean repeatAfterCompletion = task.getFlag(Task.FLAGS, Task.FLAG_REPEAT_AFTER_COMPLETION); boolean repeatAfterCompletion = task.getFlag(Task.FLAGS, Task.FLAG_REPEAT_AFTER_COMPLETION);
if(task.hasDueDate() && !repeatAfterCompletion) { RRule rrule = initRRule(recurrence);
repeatFromDate = new Date(task.getValue(Task.DUE_DATE));
if(task.hasDueTime()) { // initialize startDateAsDV
repeatFrom = new DateTimeValueImpl(repeatFromDate.getYear() + 1900, Date original = setUpStartDate(task, repeatAfterCompletion);
repeatFromDate.getMonth() + 1, repeatFromDate.getDate(), DateValue startDateAsDV = setUpStartDateAsDV(task, rrule, original, repeatAfterCompletion);
repeatFromDate.getHours(), repeatFromDate.getMinutes(), repeatFromDate.getSeconds());
} else { if(rrule.getFreq() == Frequency.HOURLY)
repeatFrom = today; return handleHourlyRepeat(original, rrule);
} else
} else if (task.hasDueDate() && repeatAfterCompletion) { return invokeRecurrence(rrule, original, startDateAsDV);
if(task.hasDueTime()) { }
repeatFromDate = new Date(task.getValue(Task.DUE_DATE));
repeatFrom = new DateTimeValueImpl(today.year(), private static long invokeRecurrence(RRule rrule, Date original,
today.month(), today.day(), DateValue startDateAsDV) {
repeatFromDate.getHours(), repeatFromDate.getMinutes(), repeatFromDate.getSeconds()); long newDueDate = -1;
} else { RecurrenceIterator iterator = RecurrenceIteratorFactory.createRecurrenceIterator(rrule,
repeatFrom = today; startDateAsDV, TimeZone.getDefault());
} DateValue nextDate = startDateAsDV;
repeatFromDate = new Date();
for(int i = 0; i < 10; i++) { // ten tries then we give up
if(!iterator.hasNext())
return -1;
nextDate = iterator.next();
if(nextDate.compareTo(startDateAsDV) == 0)
continue;
newDueDate = buildNewDueDate(original, nextDate);
// detect if we finished
if(newDueDate > original.getTime())
break;
}
return newDueDate;
}
/** Compute long due date from DateValue */
private static long buildNewDueDate(Date original, DateValue nextDate) {
long newDueDate;
if(nextDate instanceof DateTimeValueImpl) {
DateTimeValueImpl newDateTime = (DateTimeValueImpl)nextDate;
Date date = new Date(Date.UTC(newDateTime.year() - 1900, newDateTime.month() - 1,
newDateTime.day(), newDateTime.hour(),
newDateTime.minute(), newDateTime.second()));
// time may be inaccurate due to DST, force time to be same
date.setHours(original.getHours());
date.setMinutes(original.getMinutes());
newDueDate = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME,
date.getTime());
} else { } else {
repeatFrom = today; newDueDate = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY,
new Date(nextDate.year() - 1900, nextDate.month() - 1,
nextDate.day()).getTime());
} }
return newDueDate;
}
// invoke the recurrence iterator /** Initialize RRule instance */
long newDueDate = -1; private static RRule initRRule(String recurrence) throws ParseException {
RRule rrule = new RRule(recurrence); RRule rrule = new RRule(recurrence);
// handle the iCalendar "byDay" field differently depending on if // handle the iCalendar "byDay" field differently depending on if
// we are weekly or otherwise // we are weekly or otherwise
if(rrule.getFreq() != Frequency.WEEKLY)
if(rrule.getFreq() != Frequency.WEEKLY) {
rrule.setByDay(Collections.EMPTY_LIST); rrule.setByDay(Collections.EMPTY_LIST);
}
if(rrule.getFreq() == Frequency.HOURLY) { return rrule;
newDueDate = task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, }
repeatFromDate.getTime() + DateUtilities.ONE_HOUR * rrule.getInterval());
} else { /** Set up repeat start date */
RecurrenceIterator iterator = RecurrenceIteratorFactory.createRecurrenceIterator(rrule, private static Date setUpStartDate(Task task, boolean repeatAfterCompletion) {
repeatFrom, TimeZone.getDefault()); Date startDate = new Date();
DateValue nextDate = repeatFrom; if(task.hasDueDate()) {
if(repeatFrom.compareTo(today) < 0) Date dueDate = new Date(task.getValue(Task.DUE_DATE));
iterator.advanceTo(today); if(!repeatAfterCompletion)
startDate = dueDate;
for(int i = 0; i < 10; i++) { // ten tries then we give up else if(task.hasDueTime()) {
if(!iterator.hasNext()) startDate.setHours(dueDate.getHours());
return -1; startDate.setMinutes(dueDate.getMinutes());
nextDate = iterator.next();
if(nextDate.compareTo(repeatFrom) == 0)
continue;
if(nextDate instanceof DateTimeValueImpl) {
DateTimeValueImpl newDateTime = (DateTimeValueImpl)nextDate;
Date date = new Date(Date.UTC(newDateTime.year() - 1900, newDateTime.month() - 1,
newDateTime.day(), newDateTime.hour(),
newDateTime.minute(), newDateTime.second()));
// time may be inaccurate due to DST, force time to be same
date.setHours(repeatFromDate.getHours());
date.setMinutes(repeatFromDate.getMinutes());
newDueDate = task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME,
date.getTime());
} else {
newDueDate = task.createDueDate(Task.URGENCY_SPECIFIC_DAY,
new Date(nextDate.year() - 1900, nextDate.month() - 1,
nextDate.day()).getTime());
}
// detect if we finished
if(newDueDate > DateUtilities.now()) {
// if byDay is set and interval > 1, we need to run again
if(rrule.getByDay().size() > 0 && rrule.getInterval() > 1) {
if(newDueDate - repeatFromDate.getTime() > DateUtilities.ONE_WEEK)
break;
} else if(newDueDate > repeatFromDate.getTime())
break;
}
} }
} }
return startDate;
}
private static DateValue setUpStartDateAsDV(Task task, RRule rrule, Date startDate,
boolean repeatAfterCompletion) {
if(newDueDate == -1) // if repeat after completion with weekdays, pre-compute
return -1; if(repeatAfterCompletion && rrule.getByDay().size() > 0) {
startDate = new Date(startDate.getTime() + DateUtilities.ONE_WEEK * rrule.getInterval() -
DateUtilities.ONE_DAY);
rrule.setInterval(1);
}
if(task.hasDueTime())
return new DateTimeValueImpl(startDate.getYear() + 1900,
startDate.getMonth() + 1, startDate.getDate(),
startDate.getHours(), startDate.getMinutes(), startDate.getSeconds());
else
return new DateValueImpl(startDate.getYear() + 1900,
startDate.getMonth() + 1, startDate.getDate());
}
private static long handleHourlyRepeat(Date startDate, RRule rrule) {
long newDueDate;
newDueDate = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME,
startDate.getTime() + DateUtilities.ONE_HOUR * rrule.getInterval());
return newDueDate; return newDueDate;
} }

@ -33,51 +33,42 @@ public class AdvancedRepeatTests extends TodorooTestCase {
rrule = new RRule(); rrule = new RRule();
} }
public static void assertDatesEqual(long date, long other) { // --- date with time tests
assertEquals("Expected: " + new Date(date) + ", Actual: " + new Date(other),
date, other);
}
public void testDueDateInPast() throws ParseException { public void testDueDateSpecificTime() throws ParseException {
rrule.setInterval(1); task.setFlag(Task.FLAGS, Task.FLAG_REPEAT_AFTER_COMPLETION, false);
rrule.setFreq(Frequency.DAILY); buildRRule(1, Frequency.DAILY);
// repeat once => due date should become tomorrow
long past = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, new Date(110, 7, 1).getTime());
task.setValue(Task.DUE_DATE, past);
long tomorrow = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, DateUtilities.now() + DateUtilities.ONE_DAY);
long nextDueDate = RepeatTaskCompleteListener.computeNextDueDate(task, rrule.toIcal());
assertDatesEqual(tomorrow, nextDueDate);
// test specific day & time // test specific day & time
long pastWithTime = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, new Date(110, 7, 1, 10, 4).getTime()); long dayWithTime = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, new Date(110, 7, 1, 10, 4).getTime());
task.setValue(Task.DUE_DATE, pastWithTime); task.setValue(Task.DUE_DATE, dayWithTime);
Date date = new Date(DateUtilities.now() / 1000L * 1000L);
date.setHours(10); long nextDayWithTime = dayWithTime + DateUtilities.ONE_DAY;
date.setMinutes(4);
date.setSeconds(0);
long todayWithTime = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, date.getTime()) / 1000L * 1000L;
if(todayWithTime < DateUtilities.now())
todayWithTime += DateUtilities.ONE_DAY;
nextDueDate = RepeatTaskCompleteListener.computeNextDueDate(task, rrule.toIcal()); nextDueDate = RepeatTaskCompleteListener.computeNextDueDate(task, rrule.toIcal());
assertDatesEqual(todayWithTime, nextDueDate); assertDateTimeEquals(nextDayWithTime, nextDueDate);
} }
public void testDueDateInPastRepeatMultiple() throws ParseException { public void testCompletionDateSpecificTime() throws ParseException {
rrule.setInterval(1); task.setFlag(Task.FLAGS, Task.FLAG_REPEAT_AFTER_COMPLETION, true);
rrule.setFreq(Frequency.DAILY); buildRRule(1, Frequency.DAILY);
// test specific day & time
long dayWithTime = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, new Date(110, 7, 1, 10, 4).getTime());
task.setValue(Task.DUE_DATE, dayWithTime);
Date todayWithTime = new Date();
todayWithTime.setHours(10);
todayWithTime.setMinutes(4);
todayWithTime.setSeconds(0);
long nextDayWithTimeLong = todayWithTime.getTime();
if(nextDayWithTimeLong < DateUtilities.now())
nextDayWithTimeLong += DateUtilities.ONE_DAY;
// repeat once => due date should become tomorrow
long past = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, new Date(110, 7, 1, 0, 0, 0).getTime());
task.setValue(Task.DUE_DATE, past);
nextDueDate = RepeatTaskCompleteListener.computeNextDueDate(task, rrule.toIcal()); nextDueDate = RepeatTaskCompleteListener.computeNextDueDate(task, rrule.toIcal());
assertTrue(nextDueDate > DateUtilities.now()); assertDateTimeEquals(nextDueDate, nextDueDate);
task.setValue(Task.DUE_DATE, nextDueDate);
long evenMoreNextDueDate = RepeatTaskCompleteListener.computeNextDueDate(task, rrule.toIcal());
assertNotSame(nextDueDate, evenMoreNextDueDate);
} }
// --- in-the-past tests // --- due date tests
/** test multiple days per week - DUE DATE */ /** test multiple days per week - DUE DATE */
public void testDueDateInPastSingleWeekMultiDay() throws Exception { public void testDueDateInPastSingleWeekMultiDay() throws Exception {
@ -98,10 +89,8 @@ public class AdvancedRepeatTests extends TodorooTestCase {
assertDueDate(nextDueDate, THIS, Calendar.MONDAY); assertDueDate(nextDueDate, THIS, Calendar.MONDAY);
} }
// --- single day tests
/** test single day repeats - DUE DATE */ /** test single day repeats - DUE DATE */
public void testSingleDay() throws Exception { public void testDueDateSingleDay() throws Exception {
task.setFlag(Task.FLAGS, Task.FLAG_REPEAT_AFTER_COMPLETION, false); task.setFlag(Task.FLAGS, Task.FLAG_REPEAT_AFTER_COMPLETION, false);
buildRRule(1, Frequency.WEEKLY, Weekday.MO); buildRRule(1, Frequency.WEEKLY, Weekday.MO);
@ -131,8 +120,6 @@ public class AdvancedRepeatTests extends TodorooTestCase {
assertDueDate(nextDueDate, NEXT, Calendar.MONDAY); assertDueDate(nextDueDate, NEXT, Calendar.MONDAY);
} }
// --- multi day tests
/** test multiple days per week - DUE DATE */ /** test multiple days per week - DUE DATE */
public void testDueDateSingleWeekMultiDay() throws Exception { public void testDueDateSingleWeekMultiDay() throws Exception {
task.setFlag(Task.FLAGS, Task.FLAG_REPEAT_AFTER_COMPLETION, false); task.setFlag(Task.FLAGS, Task.FLAG_REPEAT_AFTER_COMPLETION, false);
@ -160,7 +147,7 @@ public class AdvancedRepeatTests extends TodorooTestCase {
setTaskDueDate(THIS, Calendar.SUNDAY); setTaskDueDate(THIS, Calendar.SUNDAY);
computeNextDueDate(); computeNextDueDate();
assertDueDate(nextDueDate, THIS, Calendar.MONDAY); assertDueDate(nextDueDate, NEXT, Calendar.MONDAY);
setTaskDueDate(THIS, Calendar.MONDAY); setTaskDueDate(THIS, Calendar.MONDAY);
computeNextDueDate(); computeNextDueDate();
@ -171,42 +158,56 @@ public class AdvancedRepeatTests extends TodorooTestCase {
assertDueDate(nextDueDate, NEXT, Calendar.MONDAY); assertDueDate(nextDueDate, NEXT, Calendar.MONDAY);
} }
// --- completion tests
/** test multiple days per week - COMPLETE DATE */ /** test multiple days per week - COMPLETE DATE */
public void testCompleteDateSingleWeekMultiDay() throws Exception { public void testCompleteDateSingleWeek() throws Exception {
task.setFlag(Task.FLAGS, Task.FLAG_REPEAT_AFTER_COMPLETION, true); task.setFlag(Task.FLAGS, Task.FLAG_REPEAT_AFTER_COMPLETION, true);
buildRRule(1, Frequency.WEEKLY, Weekday.MO, Weekday.WE, Weekday.FR); for(Weekday wday : Weekday.values()) {
buildRRule(1, Frequency.WEEKLY, wday);
setTaskDueDate(THIS, Calendar.SUNDAY); computeNextDueDate();
computeNextDueDate(); long expected = getDate(DateUtilities.now(), NEXT, wday.javaDayNum);
assertDueDate(nextDueDate, NEXT, Calendar.MONDAY); assertDateEquals(nextDueDate, expected);
}
setTaskDueDate(THIS, Calendar.MONDAY);
computeNextDueDate(); for(Weekday wday1 : Weekday.values()) {
assertDueDate(nextDueDate, NEXT, Calendar.WEDNESDAY); for(Weekday wday2 : Weekday.values()) {
if(wday1 == wday2)
setTaskDueDate(THIS, Calendar.FRIDAY); continue;
computeNextDueDate();
assertDueDate(nextDueDate, NEXT, Calendar.MONDAY); buildRRule(1, Frequency.WEEKLY, wday1, wday2);
long nextOne = getDate(DateUtilities.now(), NEXT, wday1.javaDayNum);
long nextTwo = getDate(DateUtilities.now(), NEXT, wday2.javaDayNum);
computeNextDueDate();
assertDateEquals(nextDueDate, Math.min(nextOne, nextTwo));
}
}
} }
/** test multiple days per week, multiple intervals - COMPLETE DATE */ /** test multiple days per week, multiple intervals - COMPLETE DATE */
public void testCompleteDateMultiWeekMultiDay() throws Exception { public void testCompleteDateMultiWeek() throws Exception {
task.setFlag(Task.FLAGS, Task.FLAG_REPEAT_AFTER_COMPLETION, true); task.setFlag(Task.FLAGS, Task.FLAG_REPEAT_AFTER_COMPLETION, true);
buildRRule(2, Frequency.WEEKLY, Weekday.MO, Weekday.WE, Weekday.FR); for(Weekday wday : Weekday.values()) {
buildRRule(2, Frequency.WEEKLY, wday);
setTaskDueDate(THIS, Calendar.SUNDAY); computeNextDueDate();
computeNextDueDate(); long expected = getDate(DateUtilities.now(), NEXT_NEXT, wday.javaDayNum);
assertDueDate(nextDueDate, NEXT_NEXT, Calendar.MONDAY); assertDateEquals(nextDueDate, expected);
}
setTaskDueDate(THIS, Calendar.MONDAY);
computeNextDueDate(); for(Weekday wday1 : Weekday.values()) {
assertDueDate(nextDueDate, NEXT_NEXT, Calendar.WEDNESDAY); for(Weekday wday2 : Weekday.values()) {
if(wday1 == wday2)
setTaskDueDate(THIS, Calendar.FRIDAY); continue;
computeNextDueDate();
assertDueDate(nextDueDate, NEXT_NEXT, Calendar.MONDAY); buildRRule(2, Frequency.WEEKLY, wday1, wday2);
long nextOne = getDate(DateUtilities.now(), NEXT_NEXT, wday1.javaDayNum);
long nextTwo = getDate(DateUtilities.now(), NEXT_NEXT, wday2.javaDayNum);
computeNextDueDate();
assertDateEquals(nextDueDate, Math.min(nextOne, nextTwo));
}
}
} }
// --- helpers // --- helpers
@ -215,7 +216,7 @@ public class AdvancedRepeatTests extends TodorooTestCase {
nextDueDate = RepeatTaskCompleteListener.computeNextDueDate(task, rrule.toIcal()); nextDueDate = RepeatTaskCompleteListener.computeNextDueDate(task, rrule.toIcal());
} }
private void buildRRule(int interval, Frequency freq, Weekday mo, Weekday... weekdays) { private void buildRRule(int interval, Frequency freq, Weekday... weekdays) {
rrule.setInterval(interval); rrule.setInterval(interval);
rrule.setFreq(freq); rrule.setFreq(freq);
setRRuleDays(rrule, weekdays); setRRuleDays(rrule, weekdays);
@ -223,7 +224,17 @@ public class AdvancedRepeatTests extends TodorooTestCase {
private void assertDueDate(long actual, int expectedWhich, int expectedDayOfWeek) { private void assertDueDate(long actual, int expectedWhich, int expectedDayOfWeek) {
long expected = getDate(task.getValue(Task.DUE_DATE), expectedWhich, expectedDayOfWeek); long expected = getDate(task.getValue(Task.DUE_DATE), expectedWhich, expectedDayOfWeek);
assertEquals("Due Date is '" + new Date(actual) + "', expected '" + new Date(expected) + "'", assertDateEquals(actual, expected);
}
public static void assertDateTimeEquals(long date, long other) {
assertEquals("Expected: " + new Date(date) + ", Actual: " + new Date(other),
date, other);
}
private void assertDateEquals(long actual, long expected) {
assertEquals("Due Date is '" + DateUtilities.getDateStringWithWeekday(getContext(), new Date(actual))
+ "', expected '" + DateUtilities.getDateStringWithWeekday(getContext(), new Date(expected)) + "'",
expected, actual); expected, actual);
} }
@ -238,8 +249,7 @@ public class AdvancedRepeatTests extends TodorooTestCase {
private void setTaskDueDate(int which, int day) { private void setTaskDueDate(int which, int day) {
long time = getDate(DateUtilities.now(), which, day); long time = getDate(DateUtilities.now(), which, day);
task.setValue(Task.DUE_DATE, Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, task.setValue(Task.DUE_DATE, time);
time));
} }
private long getDate(long start, int which, int dayOfWeek) { private long getDate(long start, int which, int dayOfWeek) {
@ -252,11 +262,7 @@ public class AdvancedRepeatTests extends TodorooTestCase {
c.add(Calendar.DAY_OF_MONTH, direction * 7); c.add(Calendar.DAY_OF_MONTH, direction * 7);
while(c.get(Calendar.DAY_OF_WEEK) != dayOfWeek) while(c.get(Calendar.DAY_OF_WEEK) != dayOfWeek)
c.add(Calendar.DAY_OF_MONTH, direction); c.add(Calendar.DAY_OF_MONTH, direction);
c.set(Calendar.HOUR, 0); return Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, c.getTimeInMillis());
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
return c.getTimeInMillis();
} }
} }

Loading…
Cancel
Save