1 /*
2 **
3 ** Copyright 2009, The Android Open Source Project
4 **
5 ** Licensed under the Apache License, Version 2.0 (the "License");
6 ** you may not use this file except in compliance with the License.
7 ** You may obtain a copy of the License at
8 **
9 **     http://www.apache.org/licenses/LICENSE-2.0
10 **
11 ** Unless required by applicable law or agreed to in writing, software
12 ** distributed under the License is distributed on an "AS IS" BASIS,
13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 ** See the License for the specific language governing permissions and
15 ** limitations under the License.
16 */
17 
18 /* Based on http://code.google.com/p/google-rfc-2445/source/browse/trunk/test/com/google/ical/iter/RRuleIteratorImplTest.java */
19 
20 package com.android.calendarcommon2;
21 
22 import com.android.calendarcommon2.RecurrenceSet;
23 
24 import android.os.Debug;
25 import android.test.suitebuilder.annotation.MediumTest;
26 import android.test.suitebuilder.annotation.Suppress;
27 import android.text.format.Time;
28 import junit.framework.TestCase;
29 
30 /**
31  * You can run those tests with:
32  *
33  * adb shell am instrument
34  * -e debug false
35  * -w
36  * -e
37  * class com.android.providers.calendar.RRuleTest
38  * com.android.providers.calendar.tests/android.test.InstrumentationTestRunner
39  */
40 
41 public class RRuleTest extends TestCase {
42     private static final String TAG = "RRuleTest";
43     private static final boolean METHOD_TRACE = false;
44 
getFormattedDates(long[] dates, Time time, boolean truncate)45     private static String[] getFormattedDates(long[] dates, Time time, boolean truncate) {
46         String[] out = new String[dates.length];
47         int i = 0;
48         for (long date : dates) {
49             time.set(date);
50             if (truncate) {
51                 out[i] = time.format2445().substring(0, 8); // Just YYMMDD
52             } else {
53                 out[i] = time.format2445().substring(0, 15); // YYMMDDThhmmss
54             }
55             ++i;
56         }
57         return out;
58     }
59 
60     static final String PST = "America/Los_Angeles";
61     static final String UTC = "UTC";
62      // Use this date as end of recurrence unlessotherwise specified.
63     static final String DEFAULT_END = "20091212";
64 
runRecurrenceIteratorTest(String rruleText, String dtStart, int limit, String golden)65     private void runRecurrenceIteratorTest(String rruleText, String dtStart, int limit,
66             String golden) throws Exception {
67         runRecurrenceIteratorTest(rruleText, dtStart, limit, golden, null, null, UTC);
68     }
69 
runRecurrenceIteratorTest(String rrule, String dtstartStr, int limit, String golden, String advanceTo, String tz)70     private void runRecurrenceIteratorTest(String rrule, String dtstartStr, int limit,
71             String golden, String advanceTo, String tz) throws Exception {
72         runRecurrenceIteratorTest(rrule, dtstartStr, limit, golden, advanceTo, null, tz);
73     }
74 
75     /**
76      * Tests a recurrence rule
77      * @param rrule The rule to expand
78      * @param dtstartStr The dtstart to use
79      * @param limit Maximum number of entries to expand.  if there are more, "..." is appended to
80      * the result.  Note that Android's recurrence expansion doesn't support expanding n results,
81      * so this is faked by expanding until the endAt date, and then taking limit results.
82      * @param golden The desired results
83      * @param advanceTo The starting date for expansion. dtstartStr is used if null is passed in.
84      * @param endAt The ending date.  DEFAULT_END is used if null is passed in.
85      * @param tz The time zone.  UTC is used if null is passed in.
86      * @throws Exception if anything goes wrong.
87      */
runRecurrenceIteratorTest(String rrule, String dtstartStr, int limit, String golden, String advanceTo, String endAt, String tz)88     private void runRecurrenceIteratorTest(String rrule, String dtstartStr, int limit,
89             String golden, String advanceTo, String endAt, String tz) throws Exception {
90 
91         String rdate = "";
92         String exrule = "";
93         String exdate = "";
94         rrule = rrule.replace("RRULE:", "");
95         // RecurrenceSet does not support folding of lines, so fold here
96         rrule = rrule.replace("\n ", "");
97 
98         Time dtstart = new Time(tz);
99         Time rangeStart = new Time(tz);
100         Time rangeEnd = new Time(tz);
101         Time outCal = new Time(tz);
102 
103         dtstart.parse(dtstartStr);
104         if (advanceTo == null) {
105             advanceTo = dtstartStr;
106         }
107         if (endAt == null) {
108             endAt = DEFAULT_END;
109         }
110 
111         rangeStart.parse(advanceTo);
112         rangeEnd.parse(endAt);
113 
114 
115         RecurrenceProcessor rp = new RecurrenceProcessor();
116         RecurrenceSet recur = new RecurrenceSet(rrule, rdate, exrule, exdate);
117 
118         long[] out = rp.expand(dtstart, recur, rangeStart.toMillis(false /* use isDst */),
119                 rangeEnd.toMillis(false /* use isDst */));
120 
121         if (METHOD_TRACE) {
122             Debug.stopMethodTracing();
123         }
124 
125         boolean truncate = dtstartStr.length() <= 8; // Just date, not date-time
126         String[] actual = getFormattedDates(out, outCal, truncate);
127 
128         StringBuilder sb = new StringBuilder();
129         int k = 0;
130         while (k < actual.length && --limit >= 0) {
131             if (k != 0) {
132                 sb.append(',');
133             }
134             sb.append(actual[k]);
135             k++;
136         }
137         if (limit < 0) {
138             sb.append(",...");
139         }
140         assertEquals(golden, sb.toString());
141     }
142 
143     // Infinite loop, bug 1662110
144     @MediumTest
testFrequencyLimits()145     public void testFrequencyLimits() throws Exception {
146         // Rather than checking that we get an exception,
147         // we now want to finish, but in a reasonable time
148         final long tenSeconds = 10000;
149         long start = System.currentTimeMillis();
150         runRecurrenceIteratorTest(
151                 "RRULE:FREQ=SECONDLY;BYSECOND=0,1,2,3,4,5,6,7,8,9,10,11,12,13,14," +
152                 "15,16,17,18,19,20,21,22,23,24,25,26,27,28,29," +
153                 "30,31,32,33,34,35,36,37,38,39,40,41,42,43,44," +
154                 "45,46,47,48,49,50,51,52,53,54,55,56,57,58,59", "20000101", 1, "20000101");
155         if (System.currentTimeMillis() - start > tenSeconds) {
156             fail("Don't do that");
157         }
158     }
159 
160     @MediumTest
testSimpleDaily()161     public void testSimpleDaily() throws Exception {
162         runRecurrenceIteratorTest("RRULE:FREQ=DAILY", "20060120", 5, "20060120,20060121,20060122,20060123,20060124,...");
163     }
164 
165     @MediumTest
testSimpleWeekly()166     public void testSimpleWeekly() throws Exception {
167         runRecurrenceIteratorTest("RRULE:FREQ=WEEKLY", "20060120", 5, "20060120,20060127,20060203,20060210,20060217,...");
168     }
169 
170     @MediumTest
testSimpleMonthly()171     public void testSimpleMonthly() throws Exception {
172         runRecurrenceIteratorTest("RRULE:FREQ=MONTHLY", "20060120", 5, "20060120,20060220,20060320,20060420,20060520,...");
173     }
174 
175     @MediumTest
testSimpleYearly()176     public void testSimpleYearly() throws Exception {
177         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY", "20060120", 5, "20060120,20070120,20080120,20090120,20100120,...", null, "20120101", UTC);
178     }
179 
180     // from section 4.3.10
181     @MediumTest
testMultipleByParts()182     public void testMultipleByParts() throws Exception {
183         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU", "19970105", 8, "19970105,19970112,19970119,19970126," + "19990103,19990110,19990117,19990124,...");
184     }
185 
186     @MediumTest
testCountWithInterval()187     public void testCountWithInterval() throws Exception {
188         runRecurrenceIteratorTest("RRULE:FREQ=DAILY;COUNT=10;INTERVAL=2", "19970105", 11, "19970105,19970107,19970109,19970111,19970113," + "19970115,19970117,19970119,19970121,19970123");
189     }
190 
191     // from section 4.6.5
192     // Fails: wrong dates
193     @MediumTest
194     @Suppress
testNegativeOffsetsA()195     public void testNegativeOffsetsA() throws Exception {
196         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10", "19970105", 5, "19971026,19981025,19991031,20001029,20011028,...");
197     }
198 
199     // Fails: wrong dates
200     @MediumTest
201     @Suppress
testNegativeOffsetsB()202     public void testNegativeOffsetsB() throws Exception {
203         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4", "19970105", 5, "19970406,19980405,19990404,20000402,20010401,...");
204     }
205 
206     // Fails: wrong dates
207     @MediumTest
208     @Suppress
testNegativeOffsetsC()209     public void testNegativeOffsetsC() throws Exception {
210         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4;UNTIL=19980404T150000Z", "19970105", 5, "19970406");
211     }
212 
213     // feom section 4.8.5.4
214     @MediumTest
testDailyFor10Occ()215     public void testDailyFor10Occ() throws Exception {
216         runRecurrenceIteratorTest("RRULE:FREQ=DAILY;COUNT=10", "19970902T090000", 11, "19970902T090000,19970903T090000,19970904T090000,19970905T090000," + "19970906T090000,19970907T090000,19970908T090000,19970909T090000," + "19970910T090000,19970911T090000");
217 
218     }
219 
220     @MediumTest
testDailyUntilDec4()221     public void testDailyUntilDec4() throws Exception {
222         runRecurrenceIteratorTest("RRULE:FREQ=DAILY;UNTIL=19971204", "19971128", 11, "19971128,19971129,19971130,19971201,19971202,19971203,19971204");
223     }
224 
225     // Fails: infinite loop
226     @MediumTest
227     @Suppress
testEveryOtherDayForever()228     public void testEveryOtherDayForever() throws Exception {
229         runRecurrenceIteratorTest("RRULE:FREQ=DAILY;INTERVAL=2", "19971128", 5, "19971128,19971130,19971202,19971204,19971206,...");
230     }
231 
232     @MediumTest
testEvery10Days5Occ()233     public void testEvery10Days5Occ() throws Exception {
234         runRecurrenceIteratorTest("RRULE:FREQ=DAILY;INTERVAL=10;COUNT=5", "19970902", 5, "19970902,19970912,19970922,19971002,19971012");
235     }
236 
237     @MediumTest
testWeeklyFor10Occ()238     public void testWeeklyFor10Occ() throws Exception {
239         runRecurrenceIteratorTest("RRULE:FREQ=WEEKLY;COUNT=10", "19970902", 10, "19970902,19970909,19970916,19970923,19970930," + "19971007,19971014,19971021,19971028,19971104");
240     }
241 
242     @MediumTest
testWeeklyUntilDec24()243     public void testWeeklyUntilDec24() throws Exception {
244         runRecurrenceIteratorTest("RRULE:FREQ=WEEKLY;UNTIL=19971224", "19970902", 25, "19970902,19970909,19970916,19970923,19970930," + "19971007,19971014,19971021,19971028,19971104," + "19971111,19971118,19971125,19971202,19971209," + "19971216,19971223");
245     }
246 
247     @MediumTest
testEveryOtherWeekForever()248     public void testEveryOtherWeekForever() throws Exception {
249         runRecurrenceIteratorTest("RRULE:FREQ=WEEKLY;INTERVAL=2;WKST=SU", "19970902", 11, "19970902,19970916,19970930,19971014,19971028," + "19971111,19971125,19971209,19971223,19980106," + "19980120,...");
250     }
251 
252     @MediumTest
testWeeklyOnTuesdayAndThursdayFor5Weeks()253     public void testWeeklyOnTuesdayAndThursdayFor5Weeks() throws Exception {
254         // if UNTIL date does not match start date, then until date treated as
255         // occurring on midnight.
256         runRecurrenceIteratorTest("RRULE:FREQ=WEEKLY;UNTIL=19971007;WKST=SU;BYDAY=TU,TH", "19970902T090000", 11, "19970902T090000,19970904T090000,19970909T090000,19970911T090000," + "19970916T090000,19970918T090000,19970923T090000,19970925T090000," + "19970930T090000,19971002T090000");
257         runRecurrenceIteratorTest("RRULE:FREQ=WEEKLY;UNTIL=19971007T000000Z;WKST=SU;BYDAY=TU,TH", "19970902T090000", 11, "19970902T090000,19970904T090000,19970909T090000,19970911T090000," + "19970916T090000,19970918T090000,19970923T090000,19970925T090000," + "19970930T090000,19971002T090000");
258         runRecurrenceIteratorTest("RRULE:FREQ=WEEKLY;COUNT=10;WKST=SU;BYDAY=TU,TH", "19970902", 11, "19970902,19970904,19970909,19970911,19970916," + "19970918,19970923,19970925,19970930,19971002");
259     }
260 
261     @MediumTest
testEveryOtherWeekOnMWFUntilDec24()262     public void testEveryOtherWeekOnMWFUntilDec24() throws Exception {
263         runRecurrenceIteratorTest("RRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=19971224T000000Z;WKST=SU;\n" + " BYDAY=MO,WE,FR", "19970903T090000", 25, "19970903T090000,19970905T090000,19970915T090000,19970917T090000," + "19970919T090000,19970929T090000,19971001T090000,19971003T090000," + "19971013T090000,19971015T090000,19971017T090000,19971027T090000," + "19971029T090000,19971031T090000,19971110T090000,19971112T090000," + "19971114T090000,19971124T090000,19971126T090000,19971128T090000," + "19971208T090000,19971210T090000,19971212T090000,19971222T090000");
264     }
265 
266     @MediumTest
testEveryOtherWeekOnMWFUntilDec24a()267     public void testEveryOtherWeekOnMWFUntilDec24a() throws Exception {
268         // if the UNTIL date is timed, when the start is not, the time should be
269         // ignored, so we get one more instance
270         runRecurrenceIteratorTest("RRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=19971224T000000Z;WKST=SU;\n" + " BYDAY=MO,WE,FR", "19970903", 25, "19970903,19970905,19970915,19970917," + "19970919,19970929,19971001,19971003," + "19971013,19971015,19971017,19971027," + "19971029,19971031,19971110,19971112," + "19971114,19971124,19971126,19971128," + "19971208,19971210,19971212,19971222," + "19971224");
271     }
272 
273     // Fails with wrong times
274     @MediumTest
275     @Suppress
testEveryOtherWeekOnMWFUntilDec24b()276     public void testEveryOtherWeekOnMWFUntilDec24b() throws Exception {
277         // test with an alternate timezone
278         runRecurrenceIteratorTest("RRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=19971224T090000Z;WKST=SU;\n" + " BYDAY=MO,WE,FR", "19970903T090000", 25, "19970903T160000,19970905T160000,19970915T160000,19970917T160000," + "19970919T160000,19970929T160000,19971001T160000,19971003T160000," + "19971013T160000,19971015T160000,19971017T160000,19971027T170000," + "19971029T170000,19971031T170000,19971110T170000,19971112T170000," + "19971114T170000,19971124T170000,19971126T170000,19971128T170000," + "19971208T170000,19971210T170000,19971212T170000,19971222T170000", null,  PST);
279     }
280 
281     @MediumTest
testEveryOtherWeekOnTuThFor8Occ()282     public void testEveryOtherWeekOnTuThFor8Occ() throws Exception {
283         runRecurrenceIteratorTest("RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=8;WKST=SU;BYDAY=TU,TH", "19970902", 8, "19970902,19970904,19970916,19970918,19970930," + "19971002,19971014,19971016");
284     }
285 
286     @MediumTest
testMonthlyOnThe1stFridayFor10Occ()287     public void testMonthlyOnThe1stFridayFor10Occ() throws Exception {
288         runRecurrenceIteratorTest("RRULE:FREQ=MONTHLY;COUNT=10;BYDAY=1FR", "19970905", 10, "19970905,19971003,19971107,19971205,19980102," + "19980206,19980306,19980403,19980501,19980605");
289     }
290 
291     @MediumTest
testMonthlyOnThe1stFridayUntilDec24()292     public void testMonthlyOnThe1stFridayUntilDec24() throws Exception {
293         runRecurrenceIteratorTest("RRULE:FREQ=MONTHLY;UNTIL=19971224T000000Z;BYDAY=1FR", "19970905", 4, "19970905,19971003,19971107,19971205");
294     }
295 
296     @MediumTest
testEveryOtherMonthOnThe1stAndLastSundayFor10Occ()297     public void testEveryOtherMonthOnThe1stAndLastSundayFor10Occ() throws Exception {
298         runRecurrenceIteratorTest("RRULE:FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=1SU,-1SU", "19970907", 10, "19970907,19970928,19971102,19971130,19980104," + "19980125,19980301,19980329,19980503,19980531");
299     }
300 
301     @MediumTest
testMonthlyOnTheSecondToLastMondayOfTheMonthFor6Months()302     public void testMonthlyOnTheSecondToLastMondayOfTheMonthFor6Months() throws Exception {
303         runRecurrenceIteratorTest("RRULE:FREQ=MONTHLY;COUNT=6;BYDAY=-2MO", "19970922", 6, "19970922,19971020,19971117,19971222,19980119," + "19980216");
304     }
305 
306     @MediumTest
testMonthlyOnTheThirdToLastDay()307     public void testMonthlyOnTheThirdToLastDay() throws Exception {
308         runRecurrenceIteratorTest("RRULE:FREQ=MONTHLY;BYMONTHDAY=-3", "19970928", 6, "19970928,19971029,19971128,19971229,19980129,19980226,...");
309     }
310 
311     @MediumTest
testMonthlyOnThe2ndAnd15thFor10Occ()312     public void testMonthlyOnThe2ndAnd15thFor10Occ() throws Exception {
313         runRecurrenceIteratorTest("RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=2,15", "19970902", 10, "19970902,19970915,19971002,19971015,19971102," + "19971115,19971202,19971215,19980102,19980115");
314     }
315 
316     @MediumTest
testMonthlyOnTheFirstAndLastFor10Occ()317     public void testMonthlyOnTheFirstAndLastFor10Occ() throws Exception {
318         runRecurrenceIteratorTest("RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=1,-1", "19970930", 10, "19970930,19971001,19971031,19971101,19971130," + "19971201,19971231,19980101,19980131,19980201");
319     }
320 
321     @MediumTest
testEvery18MonthsOnThe10thThru15thFor10Occ()322     public void testEvery18MonthsOnThe10thThru15thFor10Occ() throws Exception {
323         runRecurrenceIteratorTest("RRULE:FREQ=MONTHLY;INTERVAL=18;COUNT=10;BYMONTHDAY=10,11,12,13,14,\n" + " 15", "19970910", 10, "19970910,19970911,19970912,19970913,19970914," + "19970915,19990310,19990311,19990312,19990313");
324     }
325 
326     @MediumTest
testEveryTuesdayEveryOtherMonth()327     public void testEveryTuesdayEveryOtherMonth() throws Exception {
328         runRecurrenceIteratorTest("RRULE:FREQ=MONTHLY;INTERVAL=2;BYDAY=TU", "19970902", 18, "19970902,19970909,19970916,19970923,19970930," + "19971104,19971111,19971118,19971125,19980106," + "19980113,19980120,19980127,19980303,19980310," + "19980317,19980324,19980331,...");
329     }
330 
331     @MediumTest
testYearlyInJuneAndJulyFor10Occurrences()332     public void testYearlyInJuneAndJulyFor10Occurrences() throws Exception {
333         // Note: Since none of the BYDAY, BYMONTHDAY or BYYEARDAY components
334         // are specified, the day is gotten from DTSTART
335         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;COUNT=10;BYMONTH=6,7", "19970610", 10, "19970610,19970710,19980610,19980710,19990610," + "19990710,20000610,20000710,20010610,20010710");
336     }
337 
338     @MediumTest
testEveryOtherYearOnJanuaryFebruaryAndMarchFor10Occurrences()339     public void testEveryOtherYearOnJanuaryFebruaryAndMarchFor10Occurrences() throws Exception {
340         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;INTERVAL=2;COUNT=10;BYMONTH=1,2,3", "19970310", 10, "19970310,19990110,19990210,19990310,20010110," + "20010210,20010310,20030110,20030210,20030310");
341     }
342 
343     //Fails: wrong dates
344     @MediumTest
345     @Suppress
testEvery3rdYearOnThe1st100thAnd200thDayFor10Occurrences()346     public void testEvery3rdYearOnThe1st100thAnd200thDayFor10Occurrences() throws Exception {
347         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;INTERVAL=3;COUNT=10;BYYEARDAY=1,100,200", "19970101", 10, "19970101,19970410,19970719,20000101,20000409," + "20000718,20030101,20030410,20030719,20060101");
348     }
349 
350     // Fails: infinite loop
351     @MediumTest
352     @Suppress
testEvery20thMondayOfTheYearForever()353     public void testEvery20thMondayOfTheYearForever() throws Exception {
354         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;BYDAY=20MO", "19970519", 3, "19970519,19980518,19990517,...");
355     }
356 
357     // Fails: generates wrong dates
358     @MediumTest
359     @Suppress
testMondayOfWeekNumber20WhereTheDefaultStartOfTheWeekIsMonday()360     public void testMondayOfWeekNumber20WhereTheDefaultStartOfTheWeekIsMonday() throws Exception {
361         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;BYWEEKNO=20;BYDAY=MO", "19970512", 3, "19970512,19980511,19990517,...");
362     }
363 
364     @MediumTest
testEveryThursdayInMarchForever()365     public void testEveryThursdayInMarchForever() throws Exception {
366         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=TH", "19970313", 11, "19970313,19970320,19970327,19980305,19980312," + "19980319,19980326,19990304,19990311,19990318," + "19990325,...");
367     }
368 
369     //Fails: wrong dates
370     @MediumTest
371     @Suppress
testEveryThursdayButOnlyDuringJuneJulyAndAugustForever()372     public void testEveryThursdayButOnlyDuringJuneJulyAndAugustForever() throws Exception {
373         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;BYDAY=TH;BYMONTH=6,7,8", "19970605", 39, "19970605,19970612,19970619,19970626,19970703," + "19970710,19970717,19970724,19970731,19970807," + "19970814,19970821,19970828,19980604,19980611," + "19980618,19980625,19980702,19980709,19980716," + "19980723,19980730,19980806,19980813,19980820," + "19980827,19990603,19990610,19990617,19990624," + "19990701,19990708,19990715,19990722,19990729," + "19990805,19990812,19990819,19990826,...");
374     }
375 
376     //Fails: infinite loop
377     @MediumTest
378     @Suppress
testEveryFridayThe13thForever()379     public void testEveryFridayThe13thForever() throws Exception {
380         runRecurrenceIteratorTest("RRULE:FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13", "19970902", 5, "19980213,19980313,19981113,19990813,20001013," + "...");
381     }
382 
383     @MediumTest
testTheFirstSaturdayThatFollowsTheFirstSundayOfTheMonthForever()384     public void testTheFirstSaturdayThatFollowsTheFirstSundayOfTheMonthForever() throws Exception {
385         runRecurrenceIteratorTest("RRULE:FREQ=MONTHLY;BYDAY=SA;BYMONTHDAY=7,8,9,10,11,12,13", "19970913", 10, "19970913,19971011,19971108,19971213,19980110," + "19980207,19980307,19980411,19980509,19980613," + "...");
386     }
387 
388     @MediumTest
testEvery4YearsThe1stTuesAfterAMonInNovForever()389     public void testEvery4YearsThe1stTuesAfterAMonInNovForever() throws Exception {
390         // US Presidential Election Day
391         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;INTERVAL=4;BYMONTH=11;BYDAY=TU;BYMONTHDAY=2,3,4,\n" + " 5,6,7,8", "19961105", 3, "19961105,20001107,20041102,...");
392     }
393 
394     // Fails: wrong dates
395     @MediumTest
396     @Suppress
testThe3rdInstanceIntoTheMonthOfOneOfTuesWedThursForNext3Months()397     public void testThe3rdInstanceIntoTheMonthOfOneOfTuesWedThursForNext3Months() throws Exception {
398         runRecurrenceIteratorTest("RRULE:FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=3", "19970904", 3, "19970904,19971007,19971106");
399     }
400 
401     // Fails: wrong dates
402     @MediumTest
403     @Suppress
testThe2ndToLastWeekdayOfTheMonth()404     public void testThe2ndToLastWeekdayOfTheMonth() throws Exception {
405         runRecurrenceIteratorTest("RRULE:FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-2", "19970929", 7, "19970929,19971030,19971127,19971230,19980129," + "19980226,19980330,...");
406     }
407 
408     // Fails: infinite loop
409     @MediumTest
410     @Suppress
testEvery3HoursFrom900AmTo500PmOnASpecificDay()411     public void testEvery3HoursFrom900AmTo500PmOnASpecificDay() throws Exception {
412         runRecurrenceIteratorTest("RRULE:FREQ=HOURLY;INTERVAL=3;UNTIL=19970903T010000Z", "19970902", 7, "00000902,19970909,19970900,19970912,19970900," + "19970915,19970900");
413     }
414 
415     // Fails: infinite loop
416     @MediumTest
417     @Suppress
testEvery15MinutesFor6Occurrences()418     public void testEvery15MinutesFor6Occurrences() throws Exception {
419         runRecurrenceIteratorTest("RRULE:FREQ=MINUTELY;INTERVAL=15;COUNT=6", "19970902", 13, "00000902,19970909,19970900,19970909,19970915," + "19970909,19970930,19970909,19970945,19970910," + "19970900,19970910,19970915");
420     }
421 
422     @MediumTest
423     @Suppress
testEveryHourAndAHalfFor4Occurrences()424     public void testEveryHourAndAHalfFor4Occurrences() throws Exception {
425         runRecurrenceIteratorTest("RRULE:FREQ=MINUTELY;INTERVAL=90;COUNT=4", "19970902", 9, "00000902,19970909,19970900,19970910,19970930," + "19970912,19970900,19970913,19970930");
426     }
427 
428     // Fails: wrong dates
429     @MediumTest
430     @Suppress
testAnExampleWhereTheDaysGeneratedMakesADifferenceBecauseOfWkst()431     public void testAnExampleWhereTheDaysGeneratedMakesADifferenceBecauseOfWkst() throws Exception {
432         runRecurrenceIteratorTest("RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=MO", "19970805", 4, "19970805,19970810,19970819,19970824");
433     }
434 
435     // Fails: wrong dates
436     @MediumTest
437     @Suppress
testAnExampleWhereTheDaysGeneratedMakesADifferenceBecauseOfWkst2()438     public void testAnExampleWhereTheDaysGeneratedMakesADifferenceBecauseOfWkst2() throws Exception {
439         runRecurrenceIteratorTest("RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=SU", "19970805", 8, "19970805,19970817,19970819,19970831");
440     }
441 
442     // Fails: wrong dates
443     @MediumTest
444     @Suppress
testWithByDayAndByMonthDayFilter()445     public void testWithByDayAndByMonthDayFilter() throws Exception {
446         runRecurrenceIteratorTest("RRULE:FREQ=WEEKLY;COUNT=4;BYDAY=TU,SU;" + "BYMONTHDAY=13,14,15,16,17,18,19,20", "19970805", 8, "19970817,19970819,19970914,19970916");
447     }
448 
449     // Failed: wrong dates
450     @MediumTest
451     @Suppress
testAnnuallyInAugustOnTuesAndSunBetween13thAnd20th()452     public void testAnnuallyInAugustOnTuesAndSunBetween13thAnd20th() throws Exception {
453         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;COUNT=4;BYDAY=TU,SU;" + "BYMONTHDAY=13,14,15,16,17,18,19,20;BYMONTH=8", "19970605", 8, "19970817,19970819,19980816,19980818");
454     }
455 
456     // Failed: wrong dates
457     @MediumTest
458     @Suppress
testLastDayOfTheYearIsASundayOrTuesday()459     public void testLastDayOfTheYearIsASundayOrTuesday() throws Exception {
460         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;COUNT=4;BYDAY=TU,SU;BYYEARDAY=-1", "19940605", 8, "19951231,19961231,20001231,20021231");
461     }
462 
463     // Fails: wrong dates
464     @MediumTest
465     @Suppress
testLastWeekdayOfMonth()466     public void testLastWeekdayOfMonth() throws Exception {
467         runRecurrenceIteratorTest("RRULE:FREQ=MONTHLY;BYSETPOS=-1;BYDAY=-1MO,-1TU,-1WE,-1TH,-1FR", "19940605", 8, "19940630,19940729,19940831,19940930," + "19941031,19941130,19941230,19950131,...");
468     }
469 
470     // Fails: generates wrong dates
471     @MediumTest
472     @Suppress
testMonthsThatStartOrEndOnFriday()473     public void testMonthsThatStartOrEndOnFriday() throws Exception {
474         runRecurrenceIteratorTest("RRULE:FREQ=MONTHLY;BYMONTHDAY=1,-1;BYDAY=FR;COUNT=6", "19940605", 8, "19940701,19940930,19950331,19950630,19950901,19951201");
475     }
476 
477     // Fails: can't go that far into future
478     @MediumTest
479     @Suppress
testCenturiesThatAreNotLeapYears()480     public void testCenturiesThatAreNotLeapYears() throws Exception {
481         // I can't think of a good reason anyone would want to specify both a
482         // month day and a year day, so here's a really contrived example
483         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;INTERVAL=100;BYYEARDAY=60;BYMONTHDAY=1", "19000101", 4, "19000301,21000301,22000301,23000301,...", null, "25000101", UTC);
484     }
485 
486     // Fails: generates instances when it shouldn't
487     @MediumTest
488     @Suppress
testNoInstancesGenerated()489     public void testNoInstancesGenerated() throws Exception {
490         runRecurrenceIteratorTest("RRULE:FREQ=DAILY;UNTIL=19990101", "20000101", 4, "");
491     }
492 
493     // Fails: generates instances when it shouldn't
494     @MediumTest
495     @Suppress
testNoInstancesGenerated2()496     public void testNoInstancesGenerated2() throws Exception {
497         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;BYMONTH=2;BYMONTHDAY=30", "20000101", 4, "");
498     }
499 
500     // Fails: generates instances when it shouldn't
501     @MediumTest
502     @Suppress
testNoInstancesGenerated3()503     public void testNoInstancesGenerated3() throws Exception {
504         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;INTERVAL=4;BYYEARDAY=366", "20000101", 4, "");
505     }
506 
507     //Fails: wrong dates
508     @MediumTest
509     @Suppress
testLastWeekdayOfMarch()510     public void testLastWeekdayOfMarch() throws Exception {
511         runRecurrenceIteratorTest("RRULE:FREQ=MONTHLY;BYMONTH=3;BYDAY=SA,SU;BYSETPOS=-1", "20000101", 4, "20000326,20010331,20020331,20030330,...");
512     }
513 
514     // Fails: wrong dates
515     @MediumTest
516     @Suppress
testFirstWeekdayOfMarch()517     public void testFirstWeekdayOfMarch() throws Exception {
518         runRecurrenceIteratorTest("RRULE:FREQ=MONTHLY;BYMONTH=3;BYDAY=SA,SU;BYSETPOS=1", "20000101", 4, "20000304,20010303,20020302,20030301,...");
519     }
520 
521     //     January 1999
522     // Mo Tu We Th Fr Sa Su
523     //              1  2  3    // < 4 days, so not a week
524     //  4  5  6  7  8  9 10
525 
526     //     January 2000
527     // Mo Tu We Th Fr Sa Su
528     //                 1  2    // < 4 days, so not a week
529     //  3  4  5  6  7  8  9
530 
531     //     January 2001
532     // Mo Tu We Th Fr Sa Su
533     //  1  2  3  4  5  6  7
534     //  8  9 10 11 12 13 14
535 
536     //     January 2002
537     // Mo Tu We Th Fr Sa Su
538     //     1  2  3  4  5  6
539     //  7  8  9 10 11 12 13
540 
541     /**
542      * Find the first weekday of the first week of the year.
543      * The first week of the year may be partial, and the first week is considered
544      * to be the first one with at least four days.
545      */
546     // Fails: wrong dates
547     @MediumTest
548     @Suppress
testFirstWeekdayOfFirstWeekOfYear()549     public void testFirstWeekdayOfFirstWeekOfYear() throws Exception {
550         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;BYWEEKNO=1;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=1", "19990101", 4, "19990104,20000103,20010101,20020101,...");
551     }
552 
553     // Fails: wrong dates
554     @MediumTest
555     @Suppress
testFirstSundayOfTheYear1()556     public void testFirstSundayOfTheYear1() throws Exception {
557         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;BYWEEKNO=1;BYDAY=SU", "19990101", 4, "19990110,20000109,20010107,20020106,...");
558     }
559 
560     // Fails: wrong dates
561     @MediumTest
562     @Suppress
testFirstSundayOfTheYear2()563     public void testFirstSundayOfTheYear2() throws Exception {
564         // TODO(msamuel): is this right?
565         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;BYDAY=1SU", "19990101", 4, "19990103,20000102,20010107,20020106,...");
566     }
567 
568     // Fails: wrong dates
569     @MediumTest
570     @Suppress
testFirstSundayOfTheYear3()571     public void testFirstSundayOfTheYear3() throws Exception {
572         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;BYDAY=SU;BYYEARDAY=1,2,3,4,5,6,7,8,9,10,11,12,13" + ";BYSETPOS=1", "19990101", 4, "19990103,20000102,20010107,20020106,...");
573     }
574 
575     // Fails: wrong dates
576     @MediumTest
577     @Suppress
testFirstWeekdayOfYear()578     public void testFirstWeekdayOfYear() throws Exception {
579         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=1", "19990101", 4, "19990101,20000103,20010101,20020101,...");
580     }
581 
582     // Fails: wrong dates
583     @MediumTest
584     @Suppress
testLastWeekdayOfFirstWeekOfYear()585     public void testLastWeekdayOfFirstWeekOfYear() throws Exception {
586         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;BYWEEKNO=1;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1", "19990101", 4, "19990108,20000107,20010105,20020104,...");
587     }
588 
589     //     January 1999
590     // Mo Tu We Th Fr Sa Su
591     //              1  2  3
592     //  4  5  6  7  8  9 10
593     // 11 12 13 14 15 16 17
594     // 18 19 20 21 22 23 24
595     // 25 26 27 28 29 30 31
596 
597     // Fails: wrong dates
598     @MediumTest
599     @Suppress
testSecondWeekday1()600     public void testSecondWeekday1() throws Exception {
601         runRecurrenceIteratorTest("RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=2", "19990101", 4, "19990105,19990112,19990119,19990126,...");
602     }
603 
604     //     January 1997
605     // Mo Tu We Th Fr Sa Su
606     //        1  2  3  4  5
607     //  6  7  8  9 10 11 12
608     // 13 14 15 16 17 18 19
609     // 20 21 22 23 24 25 26
610     // 27 28 29 30 31
611 
612     // Fails: wrong dates
613     @MediumTest
614     @Suppress
testSecondWeekday2()615     public void testSecondWeekday2() throws Exception {
616         runRecurrenceIteratorTest("RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=2", "19970101", 4, "19970102,19970107,19970114,19970121,...");
617     }
618 
619     // Fails: wrong dates
620     @MediumTest
621     @Suppress
testByYearDayAndByDayFilterInteraction()622     public void testByYearDayAndByDayFilterInteraction() throws Exception {
623         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;BYYEARDAY=15;BYDAY=3MO", "19990101", 4, "20010115,20070115,20180115,20240115,...");
624     }
625 
626     // Fails: wrong dates
627     @MediumTest
628     @Suppress
testByDayWithNegWeekNoAsFilter()629     public void testByDayWithNegWeekNoAsFilter() throws Exception {
630         runRecurrenceIteratorTest("RRULE:FREQ=MONTHLY;BYMONTHDAY=26;BYDAY=-1FR", "19990101", 4, "19990226,19990326,19991126,20000526,...");
631     }
632 
633     // Fails: wrong dates
634     @MediumTest
635     @Suppress
testLastWeekOfTheYear()636     public void testLastWeekOfTheYear() throws Exception {
637         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;BYWEEKNO=-1", "19990101", 6, "19991227,19991228,19991229,19991230,19991231,20001225,...");
638     }
639 
640     // Fails: not enough dates generated
641     @MediumTest
642     @Suppress
testUserSubmittedTest1()643     public void testUserSubmittedTest1() throws Exception {
644         runRecurrenceIteratorTest("RRULE:FREQ=WEEKLY;INTERVAL=2;WKST=WE;BYDAY=SU,TU,TH,SA" + ";UNTIL=20000215T113000Z", "20000127T033000", 20, "20000127T033000,20000129T033000,20000130T033000,20000201T033000," + "20000210T033000,20000212T033000,20000213T033000,20000215T033000");
645     }
646 
647     @MediumTest
testAdvanceTo1()648     public void testAdvanceTo1() throws Exception {
649         // a bunch of tests grabbed from above with an advance-to date tacked on
650 
651         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=TH", "19970313", 11,
652                 /*"19970313,19970320,19970327,"*/"19980305,19980312," + "19980319,19980326,19990304,19990311,19990318," + "19990325,20000302,20000309,20000316,...", "19970601", UTC);
653     }
654 
655     // Fails: infinite loop
656     @MediumTest
657     @Suppress
testAdvanceTo2()658     public void testAdvanceTo2() throws Exception {
659         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;BYDAY=20MO", "19970519", 3,
660                 /*"19970519,"*/"19980518,19990517,20000515,...", "19980515", UTC);
661     }
662 
663     // Fails: wrong dates
664     @MediumTest
665     @Suppress
testAdvanceTo3()666     public void testAdvanceTo3() throws Exception {
667         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;INTERVAL=3;UNTIL=20090101;BYYEARDAY=1,100,200", "19970101", 10,
668                 /*"19970101,19970410,19970719,20000101,"*/"20000409," + "20000718,20030101,20030410,20030719,20060101,20060410,20060719," + "20090101", "20000228", UTC);
669     }
670 
671     //Fails: wrong dates
672     @MediumTest
673     @Suppress
testAdvanceTo4()674     public void testAdvanceTo4() throws Exception {
675         // make sure that count preserved
676         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;INTERVAL=3;COUNT=10;BYYEARDAY=1,100,200", "19970101", 10,
677                 /*"19970101,19970410,19970719,20000101,"*/"20000409," + "20000718,20030101,20030410,20030719,20060101", "20000228", UTC);
678     }
679 
680     // Fails: too many dates
681     @MediumTest
682     @Suppress
testAdvanceTo5()683     public void testAdvanceTo5() throws Exception {
684         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;INTERVAL=2;COUNT=10;BYMONTH=1,2,3", "19970310", 10,
685                 /*"19970310,"*/"19990110,19990210,19990310,20010110," + "20010210,20010310,20030110,20030210,20030310", "19980401", UTC);
686     }
687 
688     @MediumTest
testAdvanceTo6()689     public void testAdvanceTo6() throws Exception {
690         runRecurrenceIteratorTest("RRULE:FREQ=WEEKLY;UNTIL=19971224", "19970902", 25,
691                 /*"19970902,19970909,19970916,19970923,"*/"19970930," + "19971007,19971014,19971021,19971028,19971104," + "19971111,19971118,19971125,19971202,19971209," + "19971216,19971223", "19970930", UTC);
692     }
693 
694     @MediumTest
testAdvanceTo7()695     public void testAdvanceTo7() throws Exception {
696         runRecurrenceIteratorTest("RRULE:FREQ=MONTHLY;INTERVAL=18;BYMONTHDAY=10,11,12,13,14,\n" + " 15", "19970910", 5,
697                 /*"19970910,19970911,19970912,19970913,19970914," +
698                 "19970915,"*/"19990310,19990311,19990312,19990313,19990314,...", "19990101", UTC);
699     }
700 
701     @MediumTest
testAdvanceTo8()702     public void testAdvanceTo8() throws Exception {
703         // advancing into the past
704         runRecurrenceIteratorTest("RRULE:FREQ=MONTHLY;INTERVAL=18;BYMONTHDAY=10,11,12,13,14,\n" + " 15", "19970910", 11, "19970910,19970911,19970912,19970913,19970914," + "19970915,19990310,19990311,19990312,19990313,19990314,...", "19970901", UTC);
705     }
706 
707     // Fails: wrong dates
708     @MediumTest
709     @Suppress
testAdvanceTo9()710     public void testAdvanceTo9() throws Exception {
711         // skips first instance
712         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;INTERVAL=100;BYMONTH=2;BYMONTHDAY=29", "19000101", 5,
713                 // would return 2000
714                 "24000229,28000229,32000229,36000229,40000229,...", "20040101", UTC);
715     }
716 
717     // Infinite loop in native code (bug 1686327)
718     @MediumTest
719     @Suppress
testAdvanceTo10()720     public void testAdvanceTo10() throws Exception {
721         // filter hits until date before first instnace
722         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;INTERVAL=100;BYMONTH=2;BYMONTHDAY=29;UNTIL=21000101", "19000101", 5, "", "20040101", UTC);
723     }
724 
725     // Fails: generates wrong dates
726     @MediumTest
727     @Suppress
testAdvanceTo11()728     public void testAdvanceTo11() throws Exception {
729         // advancing something that returns no instances
730         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;BYMONTH=2;BYMONTHDAY=30", "20000101", 10, "", "19970901", UTC);
731     }
732 
733     // Fails: generates wrong dates
734     @MediumTest
735     @Suppress
testAdvanceTo12()736     public void testAdvanceTo12() throws Exception {
737         // advancing something that returns no instances and has a BYSETPOS rule
738         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;BYMONTH=2;BYMONTHDAY=30,31;BYSETPOS=1", "20000101", 10, "", "19970901", UTC);
739     }
740 
741     // Fails: generates wrong dates
742     @MediumTest
743     @Suppress
testAdvanceTo13()744     public void testAdvanceTo13() throws Exception {
745         // advancing way past year generator timeout
746         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;BYMONTH=2;BYMONTHDAY=28", "20000101", 10, "", "25000101", UTC);
747     }
748 
749     /**
750      * a testcase that yielded dupes due to bysetPos evilness
751      */
752     @MediumTest
753     @Suppress
testCaseThatYieldedDupes()754     public void testCaseThatYieldedDupes() throws Exception {
755         runRecurrenceIteratorTest("RRULE:FREQ=WEEKLY;WKST=SU;INTERVAL=1;BYMONTH=9,1,12,8" + ";BYMONTHDAY=-9,-29,24;BYSETPOS=-1,-4,10,-6,-1,-10,-10,-9,-8", "20060528", 200, "20060924,20061203,20061224,20070902,20071223,20080803,20080824," + "20090823,20100103,20100124,20110123,20120902,20121223,20130922," + "20140803,20140824,20150823,20160103,20160124,20170924,20171203," + "20171224,20180902,20181223,20190922,20200823,20210103,20210124," + "20220123,20230924,20231203,20231224,20240922,20250803,20250824," + "20260823,20270103,20270124,20280123,20280924,20281203,20281224," + "20290902,20291223,20300922,20310803,20310824,20330123,20340924," + "20341203,20341224,20350902,20351223,20360803,20360824,20370823," + "20380103,20380124,20390123,20400902,20401223,20410922,20420803," + "20420824,20430823,20440103,20440124,20450924,20451203,20451224," + "20460902,20461223,20470922,20480823,20490103,20490124,20500123," + "20510924,20511203,20511224,20520922,20530803,20530824,20540823," + "20550103,20550124,20560123,20560924,20561203,20561224,20570902," + "20571223,20580922,20590803,20590824,20610123,20620924,20621203," + "20621224,20630902,20631223,20640803,20640824,20650823,20660103," + "20660124,20670123,20680902,20681223,20690922,20700803,20700824," + "20710823,20720103,20720124,20730924,20731203,20731224,20740902," + "20741223,20750922,20760823,20770103,20770124,20780123,20790924," + "20791203,20791224,20800922,20810803,20810824,20820823,20830103," + "20830124,20840123,20840924,20841203,20841224,20850902,20851223," + "20860922,20870803,20870824,20890123,20900924,20901203,20901224," + "20910902,20911223,20920803,20920824,20930823,20940103,20940124," + "20950123,20960902,20961223,20970922,20980803,20980824,20990823," + "21000103,21000124,21010123,21020924,21021203,21021224,21030902," + "21031223,21040803,21040824,21050823,21060103,21060124,21070123," + "21080902,21081223,21090922,21100803,21100824,21110823,21120103," + "21120124,21130924,21131203,21131224,21140902,21141223,21150922," + "21160823,21170103,21170124,21180123,21190924,21191203,21191224," + "21200922,21210803,21210824,21220823,...");
756     }
757 
758     @MediumTest
testEveryThursdayinMarchEachYear()759     public void testEveryThursdayinMarchEachYear() throws Exception {
760         runRecurrenceIteratorTest("RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=TH", "20100304", 9, "20100304,20100311,20100318,20100325,20110303,20110310,20110317,20110324,20110331", null, "20111231", UTC);
761     }
762 }
763