1/* //device/content/providers/pim/RecurrenceProcessor.java
2**
3** Copyright 2006, 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
18package com.android.calendarcommon2;
19
20import android.text.format.Time;
21import android.util.Log;
22
23import java.util.TreeSet;
24
25public class RecurrenceProcessor
26{
27    // these are created once and reused.
28    private Time mIterator = new Time(Time.TIMEZONE_UTC);
29    private Time mUntil = new Time(Time.TIMEZONE_UTC);
30    private StringBuilder mStringBuilder = new StringBuilder();
31    private Time mGenerated = new Time(Time.TIMEZONE_UTC);
32    private DaySet mDays = new DaySet(false);
33    // Give up after this many loops.  This is roughly 1 second of expansion.
34    private static final int MAX_ALLOWED_ITERATIONS = 2000;
35
36    public RecurrenceProcessor()
37    {
38    }
39
40    private static final String TAG = "RecurrenceProcessor";
41
42    private static final boolean SPEW = false;
43
44    /**
45     * Returns the time (millis since epoch) of the last occurrence,
46     * or -1 if the event repeats forever.  If there are no occurrences
47     * (because the exrule or exdates cancel all the occurrences) and the
48     * event does not repeat forever, then 0 is returned.
49     *
50     * This computes a conservative estimate of the last occurrence. That is,
51     * the time of the actual last occurrence might be earlier than the time
52     * returned by this method.
53     *
54     * @param dtstart the time of the first occurrence
55     * @param recur the recurrence
56     * @return an estimate of the time (in UTC milliseconds) of the last
57     * occurrence, which may be greater than the actual last occurrence
58     * @throws DateException
59     */
60    public long getLastOccurence(Time dtstart,
61                                 RecurrenceSet recur) throws DateException {
62        return getLastOccurence(dtstart, null /* no limit */, recur);
63    }
64
65    /**
66     * Returns the time (millis since epoch) of the last occurrence,
67     * or -1 if the event repeats forever.  If there are no occurrences
68     * (because the exrule or exdates cancel all the occurrences) and the
69     * event does not repeat forever, then 0 is returned.
70     *
71     * This computes a conservative estimate of the last occurrence. That is,
72     * the time of the actual last occurrence might be earlier than the time
73     * returned by this method.
74     *
75     * @param dtstart the time of the first occurrence
76     * @param maxtime the max possible time of the last occurrence. null means no limit
77     * @param recur the recurrence
78     * @return an estimate of the time (in UTC milliseconds) of the last
79     * occurrence, which may be greater than the actual last occurrence
80     * @throws DateException
81     */
82    public long getLastOccurence(Time dtstart, Time maxtime,
83                                 RecurrenceSet recur) throws DateException {
84        long lastTime = -1;
85        boolean hasCount = false;
86
87        // first see if there are any "until"s specified.  if so, use the latest
88        // until / rdate.
89        if (recur.rrules != null) {
90            for (EventRecurrence rrule : recur.rrules) {
91                if (rrule.count != 0) {
92                    hasCount = true;
93                } else if (rrule.until != null) {
94                    // according to RFC 2445, until must be in UTC.
95                    mIterator.parse(rrule.until);
96                    long untilTime = mIterator.toMillis(false /* use isDst */);
97                    if (untilTime > lastTime) {
98                        lastTime = untilTime;
99                    }
100                }
101            }
102            if (lastTime != -1 && recur.rdates != null) {
103                for (long dt : recur.rdates) {
104                    if (dt > lastTime) {
105                        lastTime = dt;
106                    }
107                }
108            }
109
110            // If there were only "until"s and no "count"s, then return the
111            // last "until" date or "rdate".
112            if (lastTime != -1 && !hasCount) {
113                return lastTime;
114            }
115        } else if (recur.rdates != null &&
116                   recur.exrules == null && recur.exdates == null) {
117            // if there are only rdates, we can just pick the last one.
118            for (long dt : recur.rdates) {
119                if (dt > lastTime) {
120                    lastTime = dt;
121                }
122            }
123            return lastTime;
124        }
125
126        // Expand the complete recurrence if there were any counts specified,
127        // or if there were rdates specified.
128        if (hasCount || recur.rdates != null || maxtime != null) {
129            // The expansion might not contain any dates if the exrule or
130            // exdates cancel all the generated dates.
131            long[] dates = expand(dtstart, recur,
132                    dtstart.toMillis(false /* use isDst */) /* range start */,
133                    (maxtime != null) ?
134                            maxtime.toMillis(false /* use isDst */) : -1 /* range end */);
135
136            // The expansion might not contain any dates if exrule or exdates
137            // cancel all the generated dates.
138            if (dates.length == 0) {
139                return 0;
140            }
141            return dates[dates.length - 1];
142        }
143        return -1;
144    }
145
146    /**
147     * a -- list of values
148     * N -- number of values to use in a
149     * v -- value to check for
150     */
151    private static boolean listContains(int[] a, int N, int v)
152    {
153        for (int i=0; i<N; i++) {
154            if (a[i] == v) {
155                return true;
156            }
157        }
158        return false;
159    }
160
161    /**
162     * a -- list of values
163     * N -- number of values to use in a
164     * v -- value to check for
165     * max -- if a value in a is negative, add that negative value
166     *        to max and compare that instead; this is how we deal with
167     *        negative numbers being offsets from the end value
168     */
169    private static boolean listContains(int[] a, int N, int v, int max)
170    {
171        for (int i=0; i<N; i++) {
172            int w = a[i];
173            if (w > 0) {
174                if (w == v) {
175                    return true;
176                }
177            } else {
178                max += w; // w is negative
179                if (max == v) {
180                    return true;
181                }
182            }
183        }
184        return false;
185    }
186
187    /**
188     * Filter out the ones for events whose BYxxx rule is for
189     * a period greater than or equal to the period of the FREQ.
190     *
191     * Returns 0 if the event should not be filtered out
192     * Returns something else (a rule number which is useful for debugging)
193     * if the event should not be returned
194     */
195    private static int filter(EventRecurrence r, Time iterator)
196    {
197        boolean found;
198        int freq = r.freq;
199
200        if (EventRecurrence.MONTHLY >= freq) {
201            // BYMONTH
202            if (r.bymonthCount > 0) {
203                found = listContains(r.bymonth, r.bymonthCount,
204                        iterator.month + 1);
205                if (!found) {
206                    return 1;
207                }
208            }
209        }
210        if (EventRecurrence.WEEKLY >= freq) {
211            // BYWEEK -- this is just a guess.  I wonder how many events
212            // acutally use BYWEEKNO.
213            if (r.byweeknoCount > 0) {
214                found = listContains(r.byweekno, r.byweeknoCount,
215                                iterator.getWeekNumber(),
216                                iterator.getActualMaximum(Time.WEEK_NUM));
217                if (!found) {
218                    return 2;
219                }
220            }
221        }
222        if (EventRecurrence.DAILY >= freq) {
223            // BYYEARDAY
224            if (r.byyeardayCount > 0) {
225                found = listContains(r.byyearday, r.byyeardayCount,
226                                iterator.yearDay, iterator.getActualMaximum(Time.YEAR_DAY));
227                if (!found) {
228                    return 3;
229                }
230            }
231            // BYMONTHDAY
232            if (r.bymonthdayCount > 0 ) {
233                found = listContains(r.bymonthday, r.bymonthdayCount,
234                                iterator.monthDay,
235                                iterator.getActualMaximum(Time.MONTH_DAY));
236                if (!found) {
237                    return 4;
238                }
239            }
240            // BYDAY -- when filtering, we ignore the number field, because it
241            // only is meaningful when creating more events.
242byday:
243            if (r.bydayCount > 0) {
244                int a[] = r.byday;
245                int N = r.bydayCount;
246                int v = EventRecurrence.timeDay2Day(iterator.weekDay);
247                for (int i=0; i<N; i++) {
248                    if (a[i] == v) {
249                        break byday;
250                    }
251                }
252                return 5;
253            }
254        }
255        if (EventRecurrence.HOURLY >= freq) {
256            // BYHOUR
257            found = listContains(r.byhour, r.byhourCount,
258                            iterator.hour,
259                            iterator.getActualMaximum(Time.HOUR));
260            if (!found) {
261                return 6;
262            }
263        }
264        if (EventRecurrence.MINUTELY >= freq) {
265            // BYMINUTE
266            found = listContains(r.byminute, r.byminuteCount,
267                            iterator.minute,
268                            iterator.getActualMaximum(Time.MINUTE));
269            if (!found) {
270                return 7;
271            }
272        }
273        if (EventRecurrence.SECONDLY >= freq) {
274            // BYSECOND
275            found = listContains(r.bysecond, r.bysecondCount,
276                            iterator.second,
277                            iterator.getActualMaximum(Time.SECOND));
278            if (!found) {
279                return 8;
280            }
281        }
282
283        if (r.bysetposCount > 0) {
284bysetpos:
285            // BYSETPOS - we only handle rules like FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1
286            if (freq == EventRecurrence.MONTHLY && r.bydayCount > 0) {
287                // Check for stuff like BYDAY=1TU
288                for (int i = r.bydayCount - 1; i >= 0; i--) {
289                    if (r.bydayNum[i] != 0) {
290                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
291                            Log.v(TAG, "BYSETPOS not supported with these rules: " + r);
292                        }
293                        break bysetpos;
294                    }
295                }
296                if (!filterMonthlySetPos(r, iterator)) {
297                    // Not allowed, filter it out.
298                    return 9;
299                }
300            } else {
301                if (Log.isLoggable(TAG, Log.VERBOSE)) {
302                    Log.v(TAG, "BYSETPOS not supported with these rules: " + r);
303                }
304            }
305            // BYSETPOS was defined but we don't know how to handle it.  Do no filtering based
306            // on it.
307        }
308
309        // if we got to here, we didn't filter it out
310        return 0;
311    }
312
313    /**
314     * Filters out instances that don't match the BYSETPOS clause of a monthly recurrence rule.
315     * This is an awkward and inefficient way to go about it.
316     *
317     * @returns true if this instance should be kept
318     */
319    private static boolean filterMonthlySetPos(EventRecurrence r, Time instance) {
320        /*
321         * Compute the day of the week for the first day of the month.  "instance" has a
322         * day number and a DotW, so we compute the DotW of the 1st from that.  Note DotW
323         * is 0-6, where 0=SUNDAY.
324         *
325         * The basic calculation is to take the instance's "day of the week" number, subtract
326         * (day of the month - 1) mod 7, and then make sure it's positive.  We can simplify
327         * that with some algebra.
328         */
329        int dotw = (instance.weekDay - instance.monthDay + 36) % 7;
330
331        /*
332         * The byday[] values are specified as bits, so we can just OR them all
333         * together.
334         */
335        int bydayMask = 0;
336        for (int i = 0; i < r.bydayCount; i++) {
337            bydayMask |= r.byday[i];
338        }
339
340        /*
341         * Generate a set according to the BYDAY rules.  For each day of the month, determine
342         * if its day of the week is included.  If so, append it to the day set.
343         */
344        int maxDay = instance.getActualMaximum(Time.MONTH_DAY);
345        int daySet[] = new int[maxDay];
346        int daySetLength = 0;
347
348        for (int md = 1; md <= maxDay; md++) {
349            // For each month day, see if it's part of the set.  (This makes some assumptions
350            // about the exact form of the DotW constants.)
351            int dayBit = EventRecurrence.SU << dotw;
352            if ((bydayMask & dayBit) != 0) {
353                daySet[daySetLength++] = md;
354            }
355
356            dotw++;
357            if (dotw == 7)
358                dotw = 0;
359        }
360
361        /*
362         * Now walk through the BYSETPOS list and see if the instance is equal to any of the
363         * specified daySet entries.
364         */
365        for (int i = r.bysetposCount - 1; i >= 0; i--) {
366            int index = r.bysetpos[i];
367            if (index > 0) {
368                if (index > daySetLength) {
369                    continue;  // out of range
370                }
371                if (daySet[index-1] == instance.monthDay) {
372                    return true;
373                }
374            } else if (index < 0) {
375                if (daySetLength + index < 0) {
376                    continue;  // out of range
377                }
378                if (daySet[daySetLength + index] == instance.monthDay) {
379                    return true;
380                }
381            } else {
382                // should have been caught by parser
383                throw new RuntimeException("invalid bysetpos value");
384            }
385        }
386
387        return false;
388    }
389
390
391    private static final int USE_ITERATOR = 0;
392    private static final int USE_BYLIST = 1;
393
394    /**
395     * Return whether we should make this list from the BYxxx list or
396     * from the component of the iterator.
397     */
398    int generateByList(int count, int freq, int byFreq)
399    {
400        if (byFreq >= freq) {
401            return USE_ITERATOR;
402        } else {
403            if (count == 0) {
404                return USE_ITERATOR;
405            } else {
406                return USE_BYLIST;
407            }
408        }
409    }
410
411    private static boolean useBYX(int freq, int freqConstant, int count)
412    {
413        return freq > freqConstant && count > 0;
414    }
415
416    public static class DaySet
417    {
418        public DaySet(boolean zulu)
419        {
420            mTime = new Time(Time.TIMEZONE_UTC);
421        }
422
423        void setRecurrence(EventRecurrence r)
424        {
425            mYear = 0;
426            mMonth = -1;
427            mR = r;
428        }
429
430        boolean get(Time iterator, int day)
431        {
432            int realYear = iterator.year;
433            int realMonth = iterator.month;
434
435            Time t = null;
436
437            if (SPEW) {
438                Log.i(TAG, "get called with iterator=" + iterator
439                        + " " + iterator.month
440                        + "/" + iterator.monthDay
441                        + "/" + iterator.year + " day=" + day);
442            }
443            if (day < 1 || day > 28) {
444                // if might be past the end of the month, we need to normalize it
445                t = mTime;
446                t.set(day, realMonth, realYear);
447                unsafeNormalize(t);
448                realYear = t.year;
449                realMonth = t.month;
450                day = t.monthDay;
451                if (SPEW) {
452                    Log.i(TAG, "normalized t=" + t + " " + t.month
453                            + "/" + t.monthDay
454                            + "/" + t.year);
455                }
456            }
457
458            /*
459            if (true || SPEW) {
460                Log.i(TAG, "set t=" + t + " " + realMonth + "/" + day + "/" + realYear);
461            }
462            */
463            if (realYear != mYear || realMonth != mMonth) {
464                if (t == null) {
465                    t = mTime;
466                    t.set(day, realMonth, realYear);
467                    unsafeNormalize(t);
468                    if (SPEW) {
469                        Log.i(TAG, "set t=" + t + " " + t.month
470                                + "/" + t.monthDay
471                                + "/" + t.year
472                                + " realMonth=" + realMonth + " mMonth=" + mMonth);
473                    }
474                }
475                mYear = realYear;
476                mMonth = realMonth;
477                mDays = generateDaysList(t, mR);
478                if (SPEW) {
479                    Log.i(TAG, "generated days list");
480                }
481            }
482            return (mDays & (1<<day)) != 0;
483        }
484
485        /**
486         * Fill in a bit set containing the days of the month on which this
487         * will occur.
488         *
489         * Only call this if the r.freq > DAILY.  Otherwise, we should be
490         * processing the BYDAY, BYMONTHDAY, etc. as filters instead.
491         *
492         * monthOffset may be -1, 0 or 1
493         */
494        private static int generateDaysList(Time generated, EventRecurrence r)
495        {
496            int days = 0;
497
498            int i, count, v;
499            int[] byday, bydayNum, bymonthday;
500            int j, lastDayThisMonth;
501            int first; // Time.SUNDAY, etc
502            int k;
503
504            lastDayThisMonth = generated.getActualMaximum(Time.MONTH_DAY);
505
506            // BYDAY
507            count = r.bydayCount;
508            if (count > 0) {
509                // calculate the day of week for the first of this month (first)
510                j = generated.monthDay;
511                while (j >= 8) {
512                    j -= 7;
513                }
514                first = generated.weekDay;
515                if (first >= j) {
516                    first = first - j + 1;
517                } else {
518                    first = first - j + 8;
519                }
520
521                // What to do if the event is weekly:
522                // This isn't ideal, but we'll generate a month's worth of events
523                // and the code that calls this will only use the ones that matter
524                // for the current week.
525                byday = r.byday;
526                bydayNum = r.bydayNum;
527                for (i=0; i<count; i++) {
528                    v = bydayNum[i];
529                    j = EventRecurrence.day2TimeDay(byday[i]) - first + 1;
530                    if (j <= 0) {
531                        j += 7;
532                    }
533                    if (v == 0) {
534                        // v is 0, each day in the month/week
535                        for (; j<=lastDayThisMonth; j+=7) {
536                            if (SPEW) Log.i(TAG, "setting " + j + " for rule "
537                                    + v + "/" + EventRecurrence.day2TimeDay(byday[i]));
538                            days |= 1 << j;
539                        }
540                    }
541                    else if (v > 0) {
542                        // v is positive, count from the beginning of the month
543                        // -1 b/c the first one should add 0
544                        j += 7*(v-1);
545                        if (j <= lastDayThisMonth) {
546                            if (SPEW) Log.i(TAG, "setting " + j + " for rule "
547                                    + v + "/" + EventRecurrence.day2TimeDay(byday[i]));
548                            // if it's impossible, we drop it
549                            days |= 1 << j;
550                        }
551                    }
552                    else {
553                        // v is negative, count from the end of the month
554                        // find the last one
555                        for (; j<=lastDayThisMonth; j+=7) {
556                        }
557                        // v is negative
558                        // should do +1 b/c the last one should add 0, but we also
559                        // skipped the j -= 7 b/c the loop to find the last one
560                        // overshot by one week
561                        j += 7*v;
562                        if (j >= 1) {
563                            if (SPEW) Log.i(TAG, "setting " + j + " for rule "
564                                    + v + "/" + EventRecurrence.day2TimeDay(byday[i]));
565                            days |= 1 << j;
566                        }
567                    }
568                }
569            }
570
571            // BYMONTHDAY
572            // Q: What happens if we have BYMONTHDAY and BYDAY?
573            // A: I didn't see it in the spec, so in lieu of that, we'll
574            // intersect the two.  That seems reasonable to me.
575            if (r.freq > EventRecurrence.WEEKLY) {
576                count = r.bymonthdayCount;
577                if (count != 0) {
578                    bymonthday = r.bymonthday;
579                    if (r.bydayCount == 0) {
580                        for (i=0; i<count; i++) {
581                            v = bymonthday[i];
582                            if (v >= 0) {
583                                days |= 1 << v;
584                            } else {
585                                j = lastDayThisMonth + v + 1; // v is negative
586                                if (j >= 1 && j <= lastDayThisMonth) {
587                                    days |= 1 << j;
588                                }
589                            }
590                        }
591                    } else {
592                        // This is O(lastDayThisMonth*count), which is really
593                        // O(count) with a decent sized constant.
594                        for (j=1; j<=lastDayThisMonth; j++) {
595                            next_day : {
596                                if ((days&(1<<j)) != 0) {
597                                    for (i=0; i<count; i++) {
598                                        if (bymonthday[i] == j) {
599                                            break next_day;
600                                        }
601                                    }
602                                    days &= ~(1<<j);
603                                }
604                            }
605                        }
606                    }
607                }
608            }
609            return days;
610        }
611
612        private EventRecurrence mR;
613        private int mDays;
614        private Time mTime;
615        private int mYear;
616        private int mMonth;
617    }
618
619    /**
620     * Expands the recurrence within the given range using the given dtstart
621     * value. Returns an array of longs where each element is a date in UTC
622     * milliseconds. The return value is never null.  If there are no dates
623     * then an array of length zero is returned.
624     *
625     * @param dtstart a Time object representing the first occurrence
626     * @param recur the recurrence rules, including RRULE, RDATES, EXRULE, and
627     * EXDATES
628     * @param rangeStartMillis the beginning of the range to expand, in UTC
629     * milliseconds
630     * @param rangeEndMillis the non-inclusive end of the range to expand, in
631     * UTC milliseconds; use -1 for the entire range.
632     * @return an array of dates, each date is in UTC milliseconds
633     * @throws DateException
634     * @throws android.util.TimeFormatException if recur cannot be parsed
635     */
636    public long[] expand(Time dtstart,
637            RecurrenceSet recur,
638            long rangeStartMillis,
639            long rangeEndMillis) throws DateException {
640        String timezone = dtstart.timezone;
641        mIterator.clear(timezone);
642        mGenerated.clear(timezone);
643
644        // We don't need to clear the mUntil (and it wouldn't do any good to
645        // do so) because the "until" date string is specified in UTC and that
646        // sets the timezone in the mUntil Time object.
647
648        mIterator.set(rangeStartMillis);
649        long rangeStartDateValue = normDateTimeComparisonValue(mIterator);
650
651        long rangeEndDateValue;
652        if (rangeEndMillis != -1) {
653            mIterator.set(rangeEndMillis);
654            rangeEndDateValue = normDateTimeComparisonValue(mIterator);
655        } else {
656            rangeEndDateValue = Long.MAX_VALUE;
657        }
658
659        TreeSet<Long> dtSet = new TreeSet<Long>();
660
661        if (recur.rrules != null) {
662            for (EventRecurrence rrule : recur.rrules) {
663                expand(dtstart, rrule, rangeStartDateValue,
664                        rangeEndDateValue, true /* add */, dtSet);
665            }
666        }
667        if (recur.rdates != null) {
668            for (long dt : recur.rdates) {
669                // The dates are stored as milliseconds. We need to convert
670                // them to year/month/day values in the local timezone.
671                mIterator.set(dt);
672                long dtvalue = normDateTimeComparisonValue(mIterator);
673                dtSet.add(dtvalue);
674            }
675        }
676        if (recur.exrules != null) {
677            for (EventRecurrence exrule : recur.exrules) {
678                expand(dtstart, exrule, rangeStartDateValue,
679                        rangeEndDateValue, false /* remove */, dtSet);
680            }
681        }
682        if (recur.exdates != null) {
683            for (long dt : recur.exdates) {
684                // The dates are stored as milliseconds. We need to convert
685                // them to year/month/day values in the local timezone.
686                mIterator.set(dt);
687                long dtvalue = normDateTimeComparisonValue(mIterator);
688                dtSet.remove(dtvalue);
689            }
690        }
691        if (dtSet.isEmpty()) {
692            // this can happen if the recurrence does not occur within the
693            // expansion window.
694            return new long[0];
695        }
696
697        // The values in dtSet are represented in a special form that is useful
698        // for fast comparisons and that is easy to generate from year/month/day
699        // values. We need to convert these to UTC milliseconds and also to
700        // ensure that the dates are valid.
701        int len = dtSet.size();
702        long[] dates = new long[len];
703        int i = 0;
704        for (Long val: dtSet) {
705            setTimeFromLongValue(mIterator, val);
706            dates[i++] = mIterator.toMillis(true /* ignore isDst */);
707        }
708        return dates;
709    }
710
711    /**
712     * Run the recurrence algorithm.  Processes events defined in the local
713     * timezone of the event.  Return a list of iCalendar DATETIME
714     * strings containing the start date/times of the occurrences; the output
715     * times are defined in the local timezone of the event.
716     *
717     * If you want all of the events, pass Long.MAX_VALUE for rangeEndDateValue.  If you pass
718     * Long.MAX_VALUE for rangeEnd, and the event doesn't have a COUNT or UNTIL field,
719     * you'll get a DateException.
720     *
721     * @param dtstart the dtstart date as defined in RFC2445.  This
722     * {@link Time} should be in the timezone of the event.
723     * @param r the parsed recurrence, as defiend in RFC2445
724     * @param rangeStartDateValue the first date-time you care about, inclusive
725     * @param rangeEndDateValue the last date-time you care about, not inclusive (so
726     *                  if you care about everything up through and including
727     *                  Dec 22 1995, set last to Dec 23, 1995 00:00:00
728     * @param add Whether or not we should add to out, or remove from out.
729     * @param out the TreeSet you'd like to fill with the events
730     * @throws DateException
731     * @throws android.util.TimeFormatException if r cannot be parsed.
732     */
733    public void expand(Time dtstart,
734            EventRecurrence r,
735            long rangeStartDateValue,
736            long rangeEndDateValue,
737            boolean add,
738            TreeSet<Long> out) throws DateException {
739        unsafeNormalize(dtstart);
740        long dtstartDateValue = normDateTimeComparisonValue(dtstart);
741        int count = 0;
742
743        // add the dtstart instance to the recurrence, if within range.
744        // For example, if dtstart is Mar 1, 2010 and the range is Jan 1 - Apr 1,
745        // then add it here and increment count.  If the range is earlier or later,
746        // then don't add it here.  In that case, count will be incremented later
747        // inside  the loop.  It is important that count gets incremented exactly
748        // once here or in the loop for dtstart.
749        //
750        // NOTE: if DTSTART is not synchronized with the recurrence rule, the first instance
751        //       we return will not fit the RRULE pattern.
752        if (add && dtstartDateValue >= rangeStartDateValue
753                && dtstartDateValue < rangeEndDateValue) {
754            out.add(dtstartDateValue);
755            ++count;
756        }
757
758        Time iterator = mIterator;
759        Time until = mUntil;
760        StringBuilder sb = mStringBuilder;
761        Time generated = mGenerated;
762        DaySet days = mDays;
763
764        try {
765
766            days.setRecurrence(r);
767            if (rangeEndDateValue == Long.MAX_VALUE && r.until == null && r.count == 0) {
768                throw new DateException(
769                        "No range end provided for a recurrence that has no UNTIL or COUNT.");
770            }
771
772            // the top-level frequency
773            int freqField;
774            int freqAmount = r.interval;
775            int freq = r.freq;
776            switch (freq)
777            {
778                case EventRecurrence.SECONDLY:
779                    freqField = Time.SECOND;
780                    break;
781                case EventRecurrence.MINUTELY:
782                    freqField = Time.MINUTE;
783                    break;
784                case EventRecurrence.HOURLY:
785                    freqField = Time.HOUR;
786                    break;
787                case EventRecurrence.DAILY:
788                    freqField = Time.MONTH_DAY;
789                    break;
790                case EventRecurrence.WEEKLY:
791                    freqField = Time.MONTH_DAY;
792                    freqAmount = 7 * r.interval;
793                    if (freqAmount <= 0) {
794                        freqAmount = 7;
795                    }
796                    break;
797                case EventRecurrence.MONTHLY:
798                    freqField = Time.MONTH;
799                    break;
800                case EventRecurrence.YEARLY:
801                    freqField = Time.YEAR;
802                    break;
803                default:
804                    throw new DateException("bad freq=" + freq);
805            }
806            if (freqAmount <= 0) {
807                freqAmount = 1;
808            }
809
810            int bymonthCount = r.bymonthCount;
811            boolean usebymonth = useBYX(freq, EventRecurrence.MONTHLY, bymonthCount);
812            boolean useDays = freq >= EventRecurrence.WEEKLY &&
813                                 (r.bydayCount > 0 || r.bymonthdayCount > 0);
814            int byhourCount = r.byhourCount;
815            boolean usebyhour = useBYX(freq, EventRecurrence.HOURLY, byhourCount);
816            int byminuteCount = r.byminuteCount;
817            boolean usebyminute = useBYX(freq, EventRecurrence.MINUTELY, byminuteCount);
818            int bysecondCount = r.bysecondCount;
819            boolean usebysecond = useBYX(freq, EventRecurrence.SECONDLY, bysecondCount);
820
821            // initialize the iterator
822            iterator.set(dtstart);
823            if (freqField == Time.MONTH) {
824                if (useDays) {
825                    // if it's monthly, and we're going to be generating
826                    // days, set the iterator day field to 1 because sometimes
827                    // we'll skip months if it's greater than 28.
828                    // XXX Do we generate days for MONTHLY w/ BYHOUR?  If so,
829                    // we need to do this then too.
830                    iterator.monthDay = 1;
831                }
832            }
833
834            long untilDateValue;
835            if (r.until != null) {
836                // Ensure that the "until" date string is specified in UTC.
837                String untilStr = r.until;
838                // 15 is length of date-time without trailing Z e.g. "20090204T075959"
839                // A string such as 20090204 is a valid UNTIL (see RFC 2445) and the
840                // Z should not be added.
841                if (untilStr.length() == 15) {
842                    untilStr = untilStr + 'Z';
843                }
844                // The parse() method will set the timezone to UTC
845                until.parse(untilStr);
846
847                // We need the "until" year/month/day values to be in the same
848                // timezone as all the generated dates so that we can compare them
849                // using the values returned by normDateTimeComparisonValue().
850                until.switchTimezone(dtstart.timezone);
851                untilDateValue = normDateTimeComparisonValue(until);
852            } else {
853                untilDateValue = Long.MAX_VALUE;
854            }
855
856            sb.ensureCapacity(15);
857            sb.setLength(15); // TODO: pay attention to whether or not the event
858            // is an all-day one.
859
860            if (SPEW) {
861                Log.i(TAG, "expand called w/ rangeStart=" + rangeStartDateValue
862                        + " rangeEnd=" + rangeEndDateValue);
863            }
864
865            // go until the end of the range or we're done with this event
866            boolean eventEnded = false;
867            int failsafe = 0; // Avoid infinite loops
868            events: {
869                while (true) {
870                    int monthIndex = 0;
871                    if (failsafe++ > MAX_ALLOWED_ITERATIONS) { // Give up after about 1 second of processing
872                        Log.w(TAG, "Recurrence processing stuck with r=" + r + " rangeStart="
873                                  + rangeStartDateValue + " rangeEnd=" + rangeEndDateValue);
874                        break;
875                    }
876
877                    unsafeNormalize(iterator);
878
879                    int iteratorYear = iterator.year;
880                    int iteratorMonth = iterator.month + 1;
881                    int iteratorDay = iterator.monthDay;
882                    int iteratorHour = iterator.hour;
883                    int iteratorMinute = iterator.minute;
884                    int iteratorSecond = iterator.second;
885
886                    // year is never expanded -- there is no BYYEAR
887                    generated.set(iterator);
888
889                    if (SPEW) Log.i(TAG, "year=" + generated.year);
890
891                    do { // month
892                        int month = usebymonth
893                                        ? r.bymonth[monthIndex]
894                                        : iteratorMonth;
895                        month--;
896                        if (SPEW) Log.i(TAG, "  month=" + month);
897
898                        int dayIndex = 1;
899                        int lastDayToExamine = 0;
900
901                        // Use this to handle weeks that overlap the end of the month.
902                        // Keep the year and month that days is for, and generate it
903                        // when needed in the loop
904                        if (useDays) {
905                            // Determine where to start and end, don't worry if this happens
906                            // to be before dtstart or after the end, because that will be
907                            // filtered in the inner loop
908                            if (freq == EventRecurrence.WEEKLY) {
909                                /*
910                                 * iterator.weekDay indicates the day of the week (0-6, SU-SA).
911                                 * Because dayIndex might start in the middle of a week, and we're
912                                 * interested in treating a week as a unit, we want to move
913                                 * backward to the start of the week.  (This could make the
914                                 * dayIndex negative, which will be corrected by normalization
915                                 * later on.)
916                                 *
917                                 * The day that starts the week is determined by WKST, which
918                                 * defaults to MO.
919                                 *
920                                 * Example: dayIndex is Tuesday the 8th, and weeks start on
921                                 * Thursdays.  Tuesday is day 2, Thursday is day 4, so we
922                                 * want to move back (2 - 4 + 7) % 7 = 5 days to the previous
923                                 * Thursday.  If weeks started on Mondays, we would only
924                                 * need to move back (2 - 1 + 7) % 7 = 1 day.
925                                 */
926                                int weekStartAdj = (iterator.weekDay -
927                                        EventRecurrence.day2TimeDay(r.wkst) + 7) % 7;
928                                dayIndex = iterator.monthDay - weekStartAdj;
929                                lastDayToExamine = dayIndex + 6;
930                            } else {
931                                lastDayToExamine = generated
932                                    .getActualMaximum(Time.MONTH_DAY);
933                            }
934                            if (SPEW) Log.i(TAG, "dayIndex=" + dayIndex
935                                    + " lastDayToExamine=" + lastDayToExamine
936                                    + " days=" + days);
937                        }
938
939                        do { // day
940                            int day;
941                            if (useDays) {
942                                if (!days.get(iterator, dayIndex)) {
943                                    dayIndex++;
944                                    continue;
945                                } else {
946                                    day = dayIndex;
947                                }
948                            } else {
949                                day = iteratorDay;
950                            }
951                            if (SPEW) Log.i(TAG, "    day=" + day);
952
953                            // hour
954                            int hourIndex = 0;
955                            do {
956                                int hour = usebyhour
957                                                ? r.byhour[hourIndex]
958                                                : iteratorHour;
959                                if (SPEW) Log.i(TAG, "      hour=" + hour + " usebyhour=" + usebyhour);
960
961                                // minute
962                                int minuteIndex = 0;
963                                do {
964                                    int minute = usebyminute
965                                                    ? r.byminute[minuteIndex]
966                                                    : iteratorMinute;
967                                    if (SPEW) Log.i(TAG, "        minute=" + minute);
968
969                                    // second
970                                    int secondIndex = 0;
971                                    do {
972                                        int second = usebysecond
973                                                        ? r.bysecond[secondIndex]
974                                                        : iteratorSecond;
975                                        if (SPEW) Log.i(TAG, "          second=" + second);
976
977                                        // we do this here each time, because if we distribute it, we find the
978                                        // month advancing extra times, as we set the month to the 32nd, 33rd, etc.
979                                        // days.
980                                        generated.set(second, minute, hour, day, month, iteratorYear);
981                                        unsafeNormalize(generated);
982
983                                        long genDateValue = normDateTimeComparisonValue(generated);
984                                        // sometimes events get generated (BYDAY, BYHOUR, etc.) that
985                                        // are before dtstart.  Filter these.  I believe this is correct,
986                                        // but Google Calendar doesn't seem to always do this.
987                                        if (genDateValue >= dtstartDateValue) {
988                                            // filter and then add
989                                            // TODO: we don't check for stop conditions (like
990                                            //       passing the "end" date) unless the filter
991                                            //       allows the event.  Could stop sooner.
992                                            int filtered = filter(r, generated);
993                                            if (0 == filtered) {
994
995                                                // increase the count as long
996                                                // as this isn't the same
997                                                // as the first instance
998                                                // specified by the DTSTART
999                                                // (for RRULEs -- additive).
1000                                                // This condition must be the complement of the
1001                                                // condition for incrementing count at the
1002                                                // beginning of the method, so if we don't
1003                                                // increment count there, we increment it here.
1004                                                // For example, if add is set and dtstartDateValue
1005                                                // is inside the start/end range, then it was added
1006                                                // and count was incremented at the beginning.
1007                                                // If dtstartDateValue is outside the range or add
1008                                                // is not set, then we must increment count here.
1009                                                if (!(dtstartDateValue == genDateValue
1010                                                        && add
1011                                                        && dtstartDateValue >= rangeStartDateValue
1012                                                        && dtstartDateValue < rangeEndDateValue)) {
1013                                                    ++count;
1014                                                }
1015                                                // one reason we can stop is that
1016                                                // we're past the until date
1017                                                if (genDateValue > untilDateValue) {
1018                                                    if (SPEW) {
1019                                                        Log.i(TAG, "stopping b/c until="
1020                                                            + untilDateValue
1021                                                            + " generated="
1022                                                            + genDateValue);
1023                                                    }
1024                                                    break events;
1025                                                }
1026                                                // or we're past rangeEnd
1027                                                if (genDateValue >= rangeEndDateValue) {
1028                                                    if (SPEW) {
1029                                                        Log.i(TAG, "stopping b/c rangeEnd="
1030                                                                + rangeEndDateValue
1031                                                                + " generated=" + generated);
1032                                                    }
1033                                                    break events;
1034                                                }
1035
1036                                                if (genDateValue >= rangeStartDateValue) {
1037                                                    if (SPEW) {
1038                                                        Log.i(TAG, "adding date=" + generated + " filtered=" + filtered);
1039                                                    }
1040                                                    if (add) {
1041                                                        out.add(genDateValue);
1042                                                    } else {
1043                                                        out.remove(genDateValue);
1044                                                    }
1045                                                }
1046                                                // another is that count is high enough
1047                                                if (r.count > 0 && r.count == count) {
1048                                                    //Log.i(TAG, "stopping b/c count=" + count);
1049                                                    break events;
1050                                                }
1051                                            }
1052                                        }
1053                                        secondIndex++;
1054                                    } while (usebysecond && secondIndex < bysecondCount);
1055                                    minuteIndex++;
1056                                } while (usebyminute && minuteIndex < byminuteCount);
1057                                hourIndex++;
1058                            } while (usebyhour && hourIndex < byhourCount);
1059                            dayIndex++;
1060                        } while (useDays && dayIndex <= lastDayToExamine);
1061                        monthIndex++;
1062                    } while (usebymonth && monthIndex < bymonthCount);
1063
1064                    // Add freqAmount to freqField until we get another date that we want.
1065                    // We don't want to "generate" dates with the iterator.
1066                    // XXX: We do this for days, because there is a varying number of days
1067                    // per month
1068                    int oldDay = iterator.monthDay;
1069                    generated.set(iterator);  // just using generated as a temporary.
1070                    int n = 1;
1071                    while (true) {
1072                        int value = freqAmount * n;
1073                        switch (freqField) {
1074                            case Time.SECOND:
1075                                iterator.second += value;
1076                                break;
1077                            case Time.MINUTE:
1078                                iterator.minute += value;
1079                                break;
1080                            case Time.HOUR:
1081                                iterator.hour += value;
1082                                break;
1083                            case Time.MONTH_DAY:
1084                                iterator.monthDay += value;
1085                                break;
1086                            case Time.MONTH:
1087                                iterator.month += value;
1088                                break;
1089                            case Time.YEAR:
1090                                iterator.year += value;
1091                                break;
1092                            case Time.WEEK_DAY:
1093                                iterator.monthDay += value;
1094                                break;
1095                            case Time.YEAR_DAY:
1096                                iterator.monthDay += value;
1097                                break;
1098                            default:
1099                                throw new RuntimeException("bad field=" + freqField);
1100                        }
1101
1102                        unsafeNormalize(iterator);
1103                        if (freqField != Time.YEAR && freqField != Time.MONTH) {
1104                            break;
1105                        }
1106                        if (iterator.monthDay == oldDay) {
1107                            break;
1108                        }
1109                        n++;
1110                        iterator.set(generated);
1111                    }
1112                }
1113            }
1114        }
1115        catch (DateException e) {
1116            Log.w(TAG, "DateException with r=" + r + " rangeStart=" + rangeStartDateValue
1117                    + " rangeEnd=" + rangeEndDateValue);
1118            throw e;
1119        }
1120        catch (RuntimeException t) {
1121            Log.w(TAG, "RuntimeException with r=" + r + " rangeStart=" + rangeStartDateValue
1122                    + " rangeEnd=" + rangeEndDateValue);
1123            throw t;
1124        }
1125    }
1126
1127    /**
1128     * Normalizes the date fields to give a valid date, but if the time falls
1129     * in the invalid window during a transition out of Daylight Saving Time
1130     * when time jumps forward an hour, then the "normalized" value will be
1131     * invalid.
1132     * <p>
1133     * This method also computes the weekDay and yearDay fields.
1134     *
1135     * <p>
1136     * This method does not modify the fields isDst, or gmtOff.
1137     */
1138    static void unsafeNormalize(Time date) {
1139        int second = date.second;
1140        int minute = date.minute;
1141        int hour = date.hour;
1142        int monthDay = date.monthDay;
1143        int month = date.month;
1144        int year = date.year;
1145
1146        int addMinutes = ((second < 0) ? (second - 59) : second) / 60;
1147        second -= addMinutes * 60;
1148        minute += addMinutes;
1149        int addHours = ((minute < 0) ? (minute - 59) : minute) / 60;
1150        minute -= addHours * 60;
1151        hour += addHours;
1152        int addDays = ((hour < 0) ? (hour - 23) : hour) / 24;
1153        hour -= addDays * 24;
1154        monthDay += addDays;
1155
1156        // We want to make "monthDay" positive. We do this by subtracting one
1157        // from the year and adding a year's worth of days to "monthDay" in
1158        // the following loop while "monthDay" <= 0.
1159        while (monthDay <= 0) {
1160            // If month is after Feb, then add this year's length so that we
1161            // include this year's leap day, if any.
1162            // Otherwise (the month is Feb or earlier), add last year's length.
1163            // Subtract one from the year in either case. This gives the same
1164            // effective date but makes monthDay (the day of the month) much
1165            // larger. Eventually (usually in one iteration) monthDay will
1166            // be positive.
1167            int days = month > 1 ? yearLength(year) : yearLength(year - 1);
1168            monthDay += days;
1169            year -= 1;
1170        }
1171        // At this point, monthDay >= 1. Normalize the month to the range [0,11].
1172        if (month < 0) {
1173            int years = (month + 1) / 12 - 1;
1174            year += years;
1175            month -= 12 * years;
1176        } else if (month >= 12) {
1177            int years = month / 12;
1178            year += years;
1179            month -= 12 * years;
1180        }
1181        // At this point, month is in the range [0,11] and monthDay >= 1.
1182        // Now loop until the monthDay is in the correct range for the month.
1183        while (true) {
1184            // On January, check if we can jump forward a whole year.
1185            if (month == 0) {
1186                int yearLength = yearLength(year);
1187                if (monthDay > yearLength) {
1188                    year++;
1189                    monthDay -= yearLength;
1190                }
1191            }
1192            int monthLength = monthLength(year, month);
1193            if (monthDay > monthLength) {
1194                monthDay -= monthLength;
1195                month++;
1196                if (month >= 12) {
1197                    month -= 12;
1198                    year++;
1199                }
1200            } else break;
1201        }
1202        // At this point, monthDay <= the length of the current month and is
1203        // in the range [1,31].
1204
1205        date.second = second;
1206        date.minute = minute;
1207        date.hour = hour;
1208        date.monthDay = monthDay;
1209        date.month = month;
1210        date.year = year;
1211        date.weekDay = weekDay(year, month, monthDay);
1212        date.yearDay = yearDay(year, month, monthDay);
1213    }
1214
1215    /**
1216     * Returns true if the given year is a leap year.
1217     *
1218     * @param year the given year to test
1219     * @return true if the given year is a leap year.
1220     */
1221    static boolean isLeapYear(int year) {
1222        return (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0));
1223    }
1224
1225    /**
1226     * Returns the number of days in the given year.
1227     *
1228     * @param year the given year
1229     * @return the number of days in the given year.
1230     */
1231    static int yearLength(int year) {
1232        return isLeapYear(year) ? 366 : 365;
1233    }
1234
1235    private static final int[] DAYS_PER_MONTH = { 31, 28, 31, 30, 31, 30, 31,
1236            31, 30, 31, 30, 31 };
1237    private static final int[] DAYS_IN_YEAR_PRECEDING_MONTH = { 0, 31, 59, 90,
1238        120, 151, 180, 212, 243, 273, 304, 334 };
1239
1240    /**
1241     * Returns the number of days in the given month of the given year.
1242     *
1243     * @param year the given year.
1244     * @param month the given month in the range [0,11]
1245     * @return the number of days in the given month of the given year.
1246     */
1247    static int monthLength(int year, int month) {
1248        int n = DAYS_PER_MONTH[month];
1249        if (n != 28) {
1250            return n;
1251        }
1252        return isLeapYear(year) ? 29 : 28;
1253    }
1254
1255    /**
1256     * Computes the weekday, a number in the range [0,6] where Sunday=0, from
1257     * the given year, month, and day.
1258     *
1259     * @param year the year
1260     * @param month the 0-based month in the range [0,11]
1261     * @param day the 1-based day of the month in the range [1,31]
1262     * @return the weekday, a number in the range [0,6] where Sunday=0
1263     */
1264    static int weekDay(int year, int month, int day) {
1265        if (month <= 1) {
1266            month += 12;
1267            year -= 1;
1268        }
1269        return (day + (13 * month - 14) / 5 + year + year/4 - year/100 + year/400) % 7;
1270    }
1271
1272    /**
1273     * Computes the 0-based "year day", given the year, month, and day.
1274     *
1275     * @param year the year
1276     * @param month the 0-based month in the range [0,11]
1277     * @param day the 1-based day in the range [1,31]
1278     * @return the 0-based "year day", the number of days into the year
1279     */
1280    static int yearDay(int year, int month, int day) {
1281        int yearDay = DAYS_IN_YEAR_PRECEDING_MONTH[month] + day - 1;
1282        if (month >= 2 && isLeapYear(year)) {
1283            yearDay += 1;
1284        }
1285        return yearDay;
1286    }
1287
1288    /**
1289     * Converts a normalized Time value to a 64-bit long. The mapping of Time
1290     * values to longs provides a total ordering on the Time values so that
1291     * two Time values can be compared efficiently by comparing their 64-bit
1292     * long values.  This is faster than converting the Time values to UTC
1293     * millliseconds.
1294     *
1295     * @param normalized a Time object whose date and time fields have been
1296     * normalized
1297     * @return a 64-bit long value that can be used for comparing and ordering
1298     * dates and times represented by Time objects
1299     */
1300    private static final long normDateTimeComparisonValue(Time normalized) {
1301        // 37 bits for the year, 4 bits for the month, 5 bits for the monthDay,
1302        // 5 bits for the hour, 6 bits for the minute, 6 bits for the second.
1303        return ((long)normalized.year << 26) + (normalized.month << 22)
1304                + (normalized.monthDay << 17) + (normalized.hour << 12)
1305                + (normalized.minute << 6) + normalized.second;
1306    }
1307
1308    private static final void setTimeFromLongValue(Time date, long val) {
1309        date.year = (int) (val >> 26);
1310        date.month = (int) (val >> 22) & 0xf;
1311        date.monthDay = (int) (val >> 17) & 0x1f;
1312        date.hour = (int) (val >> 12) & 0x1f;
1313        date.minute = (int) (val >> 6) & 0x3f;
1314        date.second = (int) (val & 0x3f);
1315    }
1316}
1317