diff options
author | Andy McFadden <fadden@android.com> | 2011-07-20 15:15:44 -0700 |
---|---|---|
committer | Andy McFadden <fadden@android.com> | 2011-07-20 16:14:50 -0700 |
commit | dce6a0a89883ab3e76212a11a5a889407887718f (patch) | |
tree | 42055a7bed3f5a80c385630908c4f27320a3b222 | |
parent | 6d5684cdf6886a46ee993c8ec986d306472cd5b0 (diff) | |
download | calendar-dce6a0a89883ab3e76212a11a5a889407887718f.tar.gz |
Partial support for BYSETPOS
This adds support for rules like FREQ=MONTHLY;BYDAY=SA,SU;BYSETPOS=x
(for the popular "first weekday of the month" sort of recurrence).
Implementing fully general support for BYSETPOS is a bigger task.
Bug 4946753
Change-Id: I437c0cf52bbed2a191729b3891c3afda62ffb633
-rw-r--r-- | src/com/android/calendarcommon/RecurrenceProcessor.java | 113 | ||||
-rw-r--r-- | tests/src/com/android/calendarcommon/RecurrenceProcessorTest.java | 32 |
2 files changed, 142 insertions, 3 deletions
diff --git a/src/com/android/calendarcommon/RecurrenceProcessor.java b/src/com/android/calendarcommon/RecurrenceProcessor.java index 319b43e..c3ebc29 100644 --- a/src/com/android/calendarcommon/RecurrenceProcessor.java +++ b/src/com/android/calendarcommon/RecurrenceProcessor.java @@ -279,13 +279,115 @@ byday: return 8; } } - // BYSETPOS -- we might have to do this by postprocessing - // the list + + if (r.bysetposCount > 0) { +bysetpos: + // BYSETPOS - we only handle rules like FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1 + if (freq == EventRecurrence.MONTHLY && r.bydayCount > 0) { + // Check for stuff like BYDAY=1TU + for (int i = r.bydayCount - 1; i >= 0; i--) { + if (r.bydayNum[i] != 0) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "BYSETPOS not supported with these rules: " + r); + } + break bysetpos; + } + } + if (!filterMonthlySetPos(r, iterator)) { + // Not allowed, filter it out. + return 9; + } + } else { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "BYSETPOS not supported with these rules: " + r); + } + } + // BYSETPOS was defined but we don't know how to handle it. Do no filtering based + // on it. + } // if we got to here, we didn't filter it out return 0; } + /** + * Filters out instances that don't match the BYSETPOS clause of a monthly recurrence rule. + * This is an awkward and inefficient way to go about it. + * + * @returns true if this instance should be kept + */ + private static boolean filterMonthlySetPos(EventRecurrence r, Time instance) { + /* + * Compute the day of the week for the first day of the month. "instance" has a + * day number and a DotW, so we compute the DotW of the 1st from that. Note DotW + * is 0-6, where 0=SUNDAY. + * + * The basic calculation is to take the instance's "day of the week" number, subtract + * (day of the month - 1) mod 7, and then make sure it's positive. We can simplify + * that with some algebra. + */ + int dotw = (instance.weekDay - instance.monthDay + 36) % 7; + + /* + * The byday[] values are specified as bits, so we can just OR them all + * together. + */ + int bydayMask = 0; + for (int i = 0; i < r.bydayCount; i++) { + bydayMask |= r.byday[i]; + } + + /* + * Generate a set according to the BYDAY rules. For each day of the month, determine + * if its day of the week is included. If so, append it to the day set. + */ + int maxDay = instance.getActualMaximum(Time.MONTH_DAY); + int daySet[] = new int[maxDay]; + int daySetLength = 0; + + for (int md = 1; md <= maxDay; md++) { + // For each month day, see if it's part of the set. (This makes some assumptions + // about the exact form of the DotW constants.) + int dayBit = EventRecurrence.SU << dotw; + if ((bydayMask & dayBit) != 0) { + daySet[daySetLength++] = md; + } + + dotw++; + if (dotw == 7) + dotw = 0; + } + + /* + * Now walk through the BYSETPOS list and see if the instance is equal to any of the + * specified daySet entries. + */ + for (int i = r.bysetposCount - 1; i >= 0; i--) { + int index = r.bysetpos[i]; + if (index > 0) { + if (index > daySetLength) { + continue; // out of range + } + if (daySet[index-1] == instance.monthDay) { + return true; + } + } else if (index < 0) { + if (daySetLength + index < 0) { + continue; // out of range + } + if (daySet[daySetLength + index] == instance.monthDay) { + return true; + } + } else { + // should have been caught by parser + throw new RuntimeException("invalid bysetpos value"); + } + } + + return false; + } + + private static final int USE_ITERATOR = 0; private static final int USE_BYLIST = 1; @@ -644,6 +746,10 @@ byday: // then don't add it here. In that case, count will be incremented later // inside the loop. It is important that count gets incremented exactly // once here or in the loop for dtstart. + // + // TODO: this is only correct if DTSTART is synchronized with the recurrence rule. + // If e.g. DSTART is 2011/07/01 (a Friday), and the rule includes only "BYDAY=SA,SU", + // this will insert an incorrect entry. (Why is this being done?) if (add && dtstartDateValue >= rangeStartDateValue && dtstartDateValue < rangeEndDateValue) { out.add(dtstartDateValue); @@ -861,6 +967,9 @@ byday: // but Google Calendar doesn't seem to always do this. if (genDateValue >= dtstartDateValue) { // filter and then add + // TODO: we don't check for stop conditions (like + // passing the "end" date) unless the filter + // allows the event. Could stop sooner. int filtered = filter(r, generated); if (0 == filtered) { diff --git a/tests/src/com/android/calendarcommon/RecurrenceProcessorTest.java b/tests/src/com/android/calendarcommon/RecurrenceProcessorTest.java index 9f6f13a..2ca2d35 100644 --- a/tests/src/com/android/calendarcommon/RecurrenceProcessorTest.java +++ b/tests/src/com/android/calendarcommon/RecurrenceProcessorTest.java @@ -378,7 +378,7 @@ public class RecurrenceProcessorTest extends TestCase { null /* rdate */, null /* exrule */, null /* exdate */, "20060101T000000", "20101231T000000", new String[]{ - "20060101T100000", + "20060101T100000", // TODO: this is wrong (bug in recurrence gen) "20060113T100000", "20061013T100000", "20070413T100000", @@ -392,6 +392,36 @@ public class RecurrenceProcessorTest extends TestCase { } @SmallTest + public void testMonthly14() throws Exception { + verifyRecurrence("20110103T100000", "FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=1,-1", + null /* rdate */, null /* exrule */, null /* exdate */, + "20110101T000000", "20110331T235959", + new String[]{ + "20110103T100000", + "20110131T100000", + "20110201T100000", + "20110228T100000", + "20110301T100000", + "20110331T100000", + }); + } + + @SmallTest + public void testMonthly15() throws Exception { + verifyRecurrence("20110703T100000", "FREQ=MONTHLY;BYDAY=SA,SU;BYSETPOS=2,-2", + null /* rdate */, null /* exrule */, null /* exdate */, + "20110701T000000", "20110931T235959", + new String[]{ + "20110703T100000", + "20110730T100000", + "20110807T100000", + "20110827T100000", + "20110904T100000", + "20110924T100000", + }); + } + + @SmallTest public void testWeekly0() throws Exception { verifyRecurrence("20060215T100000", "FREQ=WEEKLY;COUNT=3", null /* rdate */, null /* exrule */, null /* exdate */, |