Support WKST in recurrence rules

RFC 5545 says WKST is significant in weekly rules with nonzero
intervals and BYDAY rules, and also in yearly rules with a BYWEEKNO
part.  Yearly with BYWEEKNO seems to be generally broken, so this
only attempts to correct weekly recurrences.

Bug 1641249

Change-Id: Icad8762be4685036fc50bed0cc75970e774a21a8
diff --git a/src/com/android/calendarcommon/EventRecurrence.java b/src/com/android/calendarcommon/EventRecurrence.java
index 57a8c20..b179071 100644
--- a/src/com/android/calendarcommon/EventRecurrence.java
+++ b/src/com/android/calendarcommon/EventRecurrence.java
@@ -581,6 +581,10 @@
          * - allows (but ignores) X-* parts
          * - improved validation on various values (e.g. UNTIL timestamps)
          * - error messages are more specific
+         *
+         * TODO: enforce additional constraints listed in RFC 5545, notably the "N/A" entries
+         * in section 3.3.10.  For example, if FREQ=WEEKLY, we should reject a rule that
+         * includes a BYMONTHDAY part.
          */
 
         /* TODO: replace with "if (freq != 0) throw" if nothing requires this */
diff --git a/src/com/android/calendarcommon/RecurrenceProcessor.java b/src/com/android/calendarcommon/RecurrenceProcessor.java
index 577ac49..82b699b 100644
--- a/src/com/android/calendarcommon/RecurrenceProcessor.java
+++ b/src/com/android/calendarcommon/RecurrenceProcessor.java
@@ -904,8 +904,26 @@
                             // to be before dtstart or after the end, because that will be
                             // filtered in the inner loop
                             if (freq == EventRecurrence.WEEKLY) {
-                                int dow = iterator.weekDay;
-                                dayIndex = iterator.monthDay - dow;
+                                /*
+                                 * iterator.weekDay indicates the day of the week (0-6, SU-SA).
+                                 * Because dayIndex might start in the middle of a week, and we're
+                                 * interested in treating a week as a unit, we want to move
+                                 * backward to the start of the week.  (This could make the
+                                 * dayIndex negative, which will be corrected by normalization
+                                 * later on.)
+                                 *
+                                 * The day that starts the week is determined by WKST, which
+                                 * defaults to MO.
+                                 *
+                                 * Example: dayIndex is Tuesday the 8th, and weeks start on
+                                 * Thursdays.  Tuesday is day 2, Thursday is day 4, so we
+                                 * want to move back (2 - 4 + 7) % 7 = 5 days to the previous
+                                 * Thursday.  If weeks started on Mondays, we would only
+                                 * need to move back (2 - 1 + 7) % 7 = 1 day.
+                                 */
+                                int weekStartAdj = (iterator.weekDay -
+                                        EventRecurrence.day2TimeDay(r.wkst) + 7) % 7;
+                                dayIndex = iterator.monthDay - weekStartAdj;
                                 lastDayToExamine = dayIndex + 6;
                             } else {
                                 lastDayToExamine = generated
diff --git a/tests/src/com/android/calendarcommon/RecurrenceProcessorTest.java b/tests/src/com/android/calendarcommon/RecurrenceProcessorTest.java
index 21d2d52..ba5ec0c 100644
--- a/tests/src/com/android/calendarcommon/RecurrenceProcessorTest.java
+++ b/tests/src/com/android/calendarcommon/RecurrenceProcessorTest.java
@@ -547,18 +547,10 @@
                 });
     }
 
-    /**
-     * This test fails because of a bug in RecurrenceProcessor.expand(). We
-     * don't have time to fix the bug yet but we don't want to lose track of
-     * this test either. The "failing" prefix on the method name prevents this
-     * test from being run. Remove the "failing" prefix when the bug is fixed.
-     *
-     * @throws Exception
-     */
     @SmallTest
-    public void failingTestWeekly9() throws Exception {
+    public void testWeekly9() throws Exception {
         verifyRecurrence("19970805T100000",
-                "FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=MO",
+                "FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU",   // uses default WKST=MO
                 null /* rdate */, null /* exrule */, null /* exdate */,
                 "19970101T000000", "19980101T000000",
                 new String[]{
@@ -611,6 +603,34 @@
         }
     }
 
+    /**
+     * Test repeating weekly event with dtstart and dtend (only one occurrence)
+     * See bug #3267616
+     * @throws Exception
+     */
+    @SmallTest
+    public void testWeekly13() throws Exception {
+        verifyRecurrence("20101117T150000",
+                "FREQ=WEEKLY;BYDAY=WE",
+                null /* rdate */, null /* exrule */, null /* exdate */,
+                "20101117T150000", "20101117T160000",
+                new String[]{ "20101117T150000" });
+    }
+
+    @SmallTest
+    public void testWeekly14() throws Exception {
+        verifyRecurrence("19970805T100000",
+                "FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=TH",
+                null /* rdate */, null /* exrule */, null /* exdate */,
+                "19970101T000000", "19980101T000000",
+                new String[]{
+                        "19970805T100000",
+                        "19970817T100000",
+                        "19970819T100000",
+                        "19970831T100000",
+                });
+    }
+
     @SmallTest
     public void testDaily0() throws Exception {
         verifyRecurrence("20060215T100000", "FREQ=DAILY;COUNT=3",
@@ -2397,18 +2417,6 @@
                 null /* last */);
     }
 
-    /**
-     * Test repeating weekly event with dtstart and dtend (only one occurrence)
-     * See bug #3267616
-     * @throws Exception
-     */
-    public void testWeekly13() throws Exception {
-        verifyRecurrence("20101117T150000",
-                "FREQ=WEEKLY;BYDAY=WE",
-                null /* rdate */, null /* exrule */, null /* exdate */,
-                "20101117T150000", "20101117T160000",
-                new String[]{ "20101117T150000" });
-    }
 
     // These recurrence rules are used in the loop that measures the performance
     // of recurrence expansion.