1747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden/* //device/content/providers/pim/RecurrenceProcessor.java
2747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden**
3747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden** Copyright 2006, The Android Open Source Project
4747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden**
5747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden** Licensed under the Apache License, Version 2.0 (the "License");
6747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden** you may not use this file except in compliance with the License.
7747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden** You may obtain a copy of the License at
8747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden**
9747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden**     http://www.apache.org/licenses/LICENSE-2.0
10747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden**
11747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden** Unless required by applicable law or agreed to in writing, software
12747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden** distributed under the License is distributed on an "AS IS" BASIS,
13747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden** See the License for the specific language governing permissions and
15747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden** limitations under the License.
16747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden*/
17747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
1806b3293d5af3454a39681cfd659271551354b8a0Michael Chanpackage com.android.calendarcommon2;
19747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
20747abc3833aec07827fa6b831e58f78e72c139d1Andy McFaddenimport android.text.format.Time;
21747abc3833aec07827fa6b831e58f78e72c139d1Andy McFaddenimport android.util.Log;
22747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
23747abc3833aec07827fa6b831e58f78e72c139d1Andy McFaddenimport java.util.TreeSet;
24747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
25747abc3833aec07827fa6b831e58f78e72c139d1Andy McFaddenpublic class RecurrenceProcessor
26747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden{
27747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    // these are created once and reused.
28747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    private Time mIterator = new Time(Time.TIMEZONE_UTC);
29747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    private Time mUntil = new Time(Time.TIMEZONE_UTC);
30747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    private StringBuilder mStringBuilder = new StringBuilder();
31747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    private Time mGenerated = new Time(Time.TIMEZONE_UTC);
32747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    private DaySet mDays = new DaySet(false);
33747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    // Give up after this many loops.  This is roughly 1 second of expansion.
34747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    private static final int MAX_ALLOWED_ITERATIONS = 2000;
35747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
36747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    public RecurrenceProcessor()
37747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    {
38747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    }
39747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
40747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    private static final String TAG = "RecurrenceProcessor";
41747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
42747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    private static final boolean SPEW = false;
43747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
44747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    /**
45747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * Returns the time (millis since epoch) of the last occurrence,
46747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * or -1 if the event repeats forever.  If there are no occurrences
47747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * (because the exrule or exdates cancel all the occurrences) and the
48747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * event does not repeat forever, then 0 is returned.
49747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     *
50747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * This computes a conservative estimate of the last occurrence. That is,
51747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * the time of the actual last occurrence might be earlier than the time
52747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * returned by this method.
53747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     *
54747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @param dtstart the time of the first occurrence
55747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @param recur the recurrence
56747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @return an estimate of the time (in UTC milliseconds) of the last
57747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * occurrence, which may be greater than the actual last occurrence
58747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @throws DateException
59747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     */
60747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    public long getLastOccurence(Time dtstart,
61747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                 RecurrenceSet recur) throws DateException {
62747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        return getLastOccurence(dtstart, null /* no limit */, recur);
63747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    }
64747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
65747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    /**
66747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * Returns the time (millis since epoch) of the last occurrence,
67747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * or -1 if the event repeats forever.  If there are no occurrences
68747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * (because the exrule or exdates cancel all the occurrences) and the
69747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * event does not repeat forever, then 0 is returned.
70747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     *
71747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * This computes a conservative estimate of the last occurrence. That is,
72747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * the time of the actual last occurrence might be earlier than the time
73747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * returned by this method.
74747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     *
75747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @param dtstart the time of the first occurrence
76747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @param maxtime the max possible time of the last occurrence. null means no limit
77747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @param recur the recurrence
78747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @return an estimate of the time (in UTC milliseconds) of the last
79747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * occurrence, which may be greater than the actual last occurrence
80747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @throws DateException
81747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     */
82747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    public long getLastOccurence(Time dtstart, Time maxtime,
83747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                 RecurrenceSet recur) throws DateException {
84747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        long lastTime = -1;
85747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        boolean hasCount = false;
86747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
87747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        // first see if there are any "until"s specified.  if so, use the latest
88747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        // until / rdate.
89747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        if (recur.rrules != null) {
90747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            for (EventRecurrence rrule : recur.rrules) {
91747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                if (rrule.count != 0) {
92747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    hasCount = true;
93747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                } else if (rrule.until != null) {
94747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    // according to RFC 2445, until must be in UTC.
95747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    mIterator.parse(rrule.until);
96747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    long untilTime = mIterator.toMillis(false /* use isDst */);
97747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    if (untilTime > lastTime) {
98747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        lastTime = untilTime;
99747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    }
100747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                }
101747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
102747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            if (lastTime != -1 && recur.rdates != null) {
103747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                for (long dt : recur.rdates) {
104747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    if (dt > lastTime) {
105747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        lastTime = dt;
106747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    }
107747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                }
108747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
109747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
110747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // If there were only "until"s and no "count"s, then return the
111747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // last "until" date or "rdate".
112747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            if (lastTime != -1 && !hasCount) {
113747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                return lastTime;
114747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
115747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        } else if (recur.rdates != null &&
116747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                   recur.exrules == null && recur.exdates == null) {
117747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // if there are only rdates, we can just pick the last one.
118747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            for (long dt : recur.rdates) {
119747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                if (dt > lastTime) {
120747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    lastTime = dt;
121747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                }
122747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
123747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            return lastTime;
124747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
125747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
126747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        // Expand the complete recurrence if there were any counts specified,
127747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        // or if there were rdates specified.
128747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        if (hasCount || recur.rdates != null || maxtime != null) {
129747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // The expansion might not contain any dates if the exrule or
130747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // exdates cancel all the generated dates.
131747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            long[] dates = expand(dtstart, recur,
132747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    dtstart.toMillis(false /* use isDst */) /* range start */,
133747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    (maxtime != null) ?
134747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            maxtime.toMillis(false /* use isDst */) : -1 /* range end */);
135747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
136747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // The expansion might not contain any dates if exrule or exdates
137747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // cancel all the generated dates.
138747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            if (dates.length == 0) {
139747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                return 0;
140747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
141747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            return dates[dates.length - 1];
142747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
143747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        return -1;
144747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    }
145747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
146747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    /**
147747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * a -- list of values
148747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * N -- number of values to use in a
149747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * v -- value to check for
150747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     */
151747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    private static boolean listContains(int[] a, int N, int v)
152747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    {
153747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        for (int i=0; i<N; i++) {
154747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            if (a[i] == v) {
155747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                return true;
156747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
157747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
158747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        return false;
159747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    }
160747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
161747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    /**
162747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * a -- list of values
163747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * N -- number of values to use in a
164747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * v -- value to check for
165747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * max -- if a value in a is negative, add that negative value
166747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     *        to max and compare that instead; this is how we deal with
167747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     *        negative numbers being offsets from the end value
168747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     */
169747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    private static boolean listContains(int[] a, int N, int v, int max)
170747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    {
171747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        for (int i=0; i<N; i++) {
172747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            int w = a[i];
173747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            if (w > 0) {
174747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                if (w == v) {
175747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    return true;
176747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                }
177747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            } else {
178747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                max += w; // w is negative
179747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                if (max == v) {
180747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    return true;
181747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                }
182747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
183747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
184747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        return false;
185747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    }
186747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
187747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    /**
188747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * Filter out the ones for events whose BYxxx rule is for
189747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * a period greater than or equal to the period of the FREQ.
190747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     *
191747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * Returns 0 if the event should not be filtered out
192747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * Returns something else (a rule number which is useful for debugging)
193747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * if the event should not be returned
194747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     */
195747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    private static int filter(EventRecurrence r, Time iterator)
196747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    {
197747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        boolean found;
198747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        int freq = r.freq;
199747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
200747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        if (EventRecurrence.MONTHLY >= freq) {
201747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // BYMONTH
202747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            if (r.bymonthCount > 0) {
203747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                found = listContains(r.bymonth, r.bymonthCount,
204747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        iterator.month + 1);
205747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                if (!found) {
206747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    return 1;
207747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                }
208747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
209747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
210747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        if (EventRecurrence.WEEKLY >= freq) {
211747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // BYWEEK -- this is just a guess.  I wonder how many events
212747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // acutally use BYWEEKNO.
213747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            if (r.byweeknoCount > 0) {
214747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                found = listContains(r.byweekno, r.byweeknoCount,
215747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                iterator.getWeekNumber(),
216747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                iterator.getActualMaximum(Time.WEEK_NUM));
217747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                if (!found) {
218747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    return 2;
219747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                }
220747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
221747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
222747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        if (EventRecurrence.DAILY >= freq) {
223747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // BYYEARDAY
224747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            if (r.byyeardayCount > 0) {
225747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                found = listContains(r.byyearday, r.byyeardayCount,
226747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                iterator.yearDay, iterator.getActualMaximum(Time.YEAR_DAY));
227747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                if (!found) {
228747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    return 3;
229747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                }
230747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
231747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // BYMONTHDAY
232747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            if (r.bymonthdayCount > 0 ) {
233747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                found = listContains(r.bymonthday, r.bymonthdayCount,
234747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                iterator.monthDay,
235747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                iterator.getActualMaximum(Time.MONTH_DAY));
236747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                if (!found) {
237747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    return 4;
238747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                }
239747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
240747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // BYDAY -- when filtering, we ignore the number field, because it
241747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // only is meaningful when creating more events.
242747abc3833aec07827fa6b831e58f78e72c139d1Andy McFaddenbyday:
243747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            if (r.bydayCount > 0) {
244747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                int a[] = r.byday;
245747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                int N = r.bydayCount;
246747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                int v = EventRecurrence.timeDay2Day(iterator.weekDay);
247747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                for (int i=0; i<N; i++) {
248747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    if (a[i] == v) {
249747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        break byday;
250747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    }
251747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                }
252747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                return 5;
253747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
254747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
255747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        if (EventRecurrence.HOURLY >= freq) {
256747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // BYHOUR
257747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            found = listContains(r.byhour, r.byhourCount,
258747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            iterator.hour,
259747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            iterator.getActualMaximum(Time.HOUR));
260747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            if (!found) {
261747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                return 6;
262747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
263747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
264747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        if (EventRecurrence.MINUTELY >= freq) {
265747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // BYMINUTE
266747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            found = listContains(r.byminute, r.byminuteCount,
267747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            iterator.minute,
268747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            iterator.getActualMaximum(Time.MINUTE));
269747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            if (!found) {
270747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                return 7;
271747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
272747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
273747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        if (EventRecurrence.SECONDLY >= freq) {
274747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // BYSECOND
275747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            found = listContains(r.bysecond, r.bysecondCount,
276747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            iterator.second,
277747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            iterator.getActualMaximum(Time.SECOND));
278747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            if (!found) {
279747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                return 8;
280747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
281747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
282dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden
283dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden        if (r.bysetposCount > 0) {
284dce6a0a89883ab3e76212a11a5a889407887718fAndy McFaddenbysetpos:
285dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden            // BYSETPOS - we only handle rules like FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1
286dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden            if (freq == EventRecurrence.MONTHLY && r.bydayCount > 0) {
287dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                // Check for stuff like BYDAY=1TU
288dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                for (int i = r.bydayCount - 1; i >= 0; i--) {
289dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                    if (r.bydayNum[i] != 0) {
290dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
291dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                            Log.v(TAG, "BYSETPOS not supported with these rules: " + r);
292dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                        }
293dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                        break bysetpos;
294dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                    }
295dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                }
296dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                if (!filterMonthlySetPos(r, iterator)) {
297dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                    // Not allowed, filter it out.
298dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                    return 9;
299dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                }
300dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden            } else {
301dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                if (Log.isLoggable(TAG, Log.VERBOSE)) {
302dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                    Log.v(TAG, "BYSETPOS not supported with these rules: " + r);
303dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                }
304dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden            }
305dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden            // BYSETPOS was defined but we don't know how to handle it.  Do no filtering based
306dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden            // on it.
307dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden        }
308747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
309747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        // if we got to here, we didn't filter it out
310747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        return 0;
311747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    }
312747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
313dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden    /**
314dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden     * Filters out instances that don't match the BYSETPOS clause of a monthly recurrence rule.
315dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden     * This is an awkward and inefficient way to go about it.
316dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden     *
317dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden     * @returns true if this instance should be kept
318dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden     */
319dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden    private static boolean filterMonthlySetPos(EventRecurrence r, Time instance) {
320dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden        /*
321dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden         * Compute the day of the week for the first day of the month.  "instance" has a
322dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden         * day number and a DotW, so we compute the DotW of the 1st from that.  Note DotW
323dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden         * is 0-6, where 0=SUNDAY.
324dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden         *
325dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden         * The basic calculation is to take the instance's "day of the week" number, subtract
326dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden         * (day of the month - 1) mod 7, and then make sure it's positive.  We can simplify
327dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden         * that with some algebra.
328dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden         */
329dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden        int dotw = (instance.weekDay - instance.monthDay + 36) % 7;
330dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden
331dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden        /*
332dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden         * The byday[] values are specified as bits, so we can just OR them all
333dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden         * together.
334dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden         */
335dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden        int bydayMask = 0;
336dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden        for (int i = 0; i < r.bydayCount; i++) {
337dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden            bydayMask |= r.byday[i];
338dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden        }
339dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden
340dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden        /*
341dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden         * Generate a set according to the BYDAY rules.  For each day of the month, determine
342dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden         * if its day of the week is included.  If so, append it to the day set.
343dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden         */
344dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden        int maxDay = instance.getActualMaximum(Time.MONTH_DAY);
345dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden        int daySet[] = new int[maxDay];
346dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden        int daySetLength = 0;
347dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden
348dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden        for (int md = 1; md <= maxDay; md++) {
349dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden            // For each month day, see if it's part of the set.  (This makes some assumptions
350dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden            // about the exact form of the DotW constants.)
351dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden            int dayBit = EventRecurrence.SU << dotw;
352dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden            if ((bydayMask & dayBit) != 0) {
353dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                daySet[daySetLength++] = md;
354dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden            }
355dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden
356dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden            dotw++;
357dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden            if (dotw == 7)
358dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                dotw = 0;
359dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden        }
360dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden
361dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden        /*
362dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden         * Now walk through the BYSETPOS list and see if the instance is equal to any of the
363dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden         * specified daySet entries.
364dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden         */
365dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden        for (int i = r.bysetposCount - 1; i >= 0; i--) {
366dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden            int index = r.bysetpos[i];
367dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden            if (index > 0) {
368dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                if (index > daySetLength) {
369dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                    continue;  // out of range
370dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                }
371dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                if (daySet[index-1] == instance.monthDay) {
372dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                    return true;
373dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                }
374dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden            } else if (index < 0) {
375dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                if (daySetLength + index < 0) {
376dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                    continue;  // out of range
377dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                }
378dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                if (daySet[daySetLength + index] == instance.monthDay) {
379dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                    return true;
380dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                }
381dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden            } else {
382dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                // should have been caught by parser
383dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                throw new RuntimeException("invalid bysetpos value");
384dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden            }
385dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden        }
386dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden
387dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden        return false;
388dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden    }
389dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden
390dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden
391747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    private static final int USE_ITERATOR = 0;
392747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    private static final int USE_BYLIST = 1;
393747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
394747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    /**
395747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * Return whether we should make this list from the BYxxx list or
396747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * from the component of the iterator.
397747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     */
398747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    int generateByList(int count, int freq, int byFreq)
399747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    {
400747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        if (byFreq >= freq) {
401747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            return USE_ITERATOR;
402747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        } else {
403747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            if (count == 0) {
404747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                return USE_ITERATOR;
405747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            } else {
406747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                return USE_BYLIST;
407747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
408747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
409747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    }
410747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
411747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    private static boolean useBYX(int freq, int freqConstant, int count)
412747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    {
413747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        return freq > freqConstant && count > 0;
414747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    }
415747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
416747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    public static class DaySet
417747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    {
418747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        public DaySet(boolean zulu)
419747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        {
420747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            mTime = new Time(Time.TIMEZONE_UTC);
421747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
422747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
423747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        void setRecurrence(EventRecurrence r)
424747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        {
425747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            mYear = 0;
426747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            mMonth = -1;
427747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            mR = r;
428747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
429747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
430747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        boolean get(Time iterator, int day)
431747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        {
432747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            int realYear = iterator.year;
433747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            int realMonth = iterator.month;
434747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
435747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            Time t = null;
436747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
437747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            if (SPEW) {
438747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                Log.i(TAG, "get called with iterator=" + iterator
439747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        + " " + iterator.month
440747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        + "/" + iterator.monthDay
441747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        + "/" + iterator.year + " day=" + day);
442747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
443747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            if (day < 1 || day > 28) {
444747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                // if might be past the end of the month, we need to normalize it
445747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                t = mTime;
446747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                t.set(day, realMonth, realYear);
447747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                unsafeNormalize(t);
448747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                realYear = t.year;
449747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                realMonth = t.month;
450747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                day = t.monthDay;
451747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                if (SPEW) {
452747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    Log.i(TAG, "normalized t=" + t + " " + t.month
453747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            + "/" + t.monthDay
454747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            + "/" + t.year);
455747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                }
456747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
457747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
458747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            /*
459747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            if (true || SPEW) {
460747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                Log.i(TAG, "set t=" + t + " " + realMonth + "/" + day + "/" + realYear);
461747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
462747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            */
463747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            if (realYear != mYear || realMonth != mMonth) {
464747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                if (t == null) {
465747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    t = mTime;
466747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    t.set(day, realMonth, realYear);
467747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    unsafeNormalize(t);
468747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    if (SPEW) {
469747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        Log.i(TAG, "set t=" + t + " " + t.month
470747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                + "/" + t.monthDay
471747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                + "/" + t.year
472747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                + " realMonth=" + realMonth + " mMonth=" + mMonth);
473747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    }
474747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                }
475747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                mYear = realYear;
476747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                mMonth = realMonth;
477747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                mDays = generateDaysList(t, mR);
478747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                if (SPEW) {
479747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    Log.i(TAG, "generated days list");
480747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                }
481747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
482747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            return (mDays & (1<<day)) != 0;
483747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
484747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
485747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        /**
486747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden         * Fill in a bit set containing the days of the month on which this
487747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden         * will occur.
488747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden         *
489747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden         * Only call this if the r.freq > DAILY.  Otherwise, we should be
490747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden         * processing the BYDAY, BYMONTHDAY, etc. as filters instead.
491747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden         *
492747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden         * monthOffset may be -1, 0 or 1
493747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden         */
494747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        private static int generateDaysList(Time generated, EventRecurrence r)
495747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        {
496747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            int days = 0;
497747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
498747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            int i, count, v;
499747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            int[] byday, bydayNum, bymonthday;
500747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            int j, lastDayThisMonth;
501747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            int first; // Time.SUNDAY, etc
502747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            int k;
503747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
504747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            lastDayThisMonth = generated.getActualMaximum(Time.MONTH_DAY);
505747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
506747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // BYDAY
507747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            count = r.bydayCount;
508747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            if (count > 0) {
509747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                // calculate the day of week for the first of this month (first)
510747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                j = generated.monthDay;
511747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                while (j >= 8) {
512747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    j -= 7;
513747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                }
514747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                first = generated.weekDay;
515747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                if (first >= j) {
516747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    first = first - j + 1;
517747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                } else {
518747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    first = first - j + 8;
519747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                }
520747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
521747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                // What to do if the event is weekly:
522747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                // This isn't ideal, but we'll generate a month's worth of events
523747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                // and the code that calls this will only use the ones that matter
524747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                // for the current week.
525747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                byday = r.byday;
526747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                bydayNum = r.bydayNum;
527747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                for (i=0; i<count; i++) {
528747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    v = bydayNum[i];
529747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    j = EventRecurrence.day2TimeDay(byday[i]) - first + 1;
530747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    if (j <= 0) {
531747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        j += 7;
532747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    }
533747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    if (v == 0) {
534747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        // v is 0, each day in the month/week
535747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        for (; j<=lastDayThisMonth; j+=7) {
536747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            if (SPEW) Log.i(TAG, "setting " + j + " for rule "
537747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                    + v + "/" + EventRecurrence.day2TimeDay(byday[i]));
538747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            days |= 1 << j;
539747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        }
540747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    }
541747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    else if (v > 0) {
542747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        // v is positive, count from the beginning of the month
543747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        // -1 b/c the first one should add 0
544747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        j += 7*(v-1);
545747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        if (j <= lastDayThisMonth) {
546747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            if (SPEW) Log.i(TAG, "setting " + j + " for rule "
547747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                    + v + "/" + EventRecurrence.day2TimeDay(byday[i]));
548747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            // if it's impossible, we drop it
549747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            days |= 1 << j;
550747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        }
551747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    }
552747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    else {
553747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        // v is negative, count from the end of the month
554747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        // find the last one
555747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        for (; j<=lastDayThisMonth; j+=7) {
556747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        }
557747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        // v is negative
558747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        // should do +1 b/c the last one should add 0, but we also
559747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        // skipped the j -= 7 b/c the loop to find the last one
560747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        // overshot by one week
561747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        j += 7*v;
562747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        if (j >= 1) {
563747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            if (SPEW) Log.i(TAG, "setting " + j + " for rule "
564747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                    + v + "/" + EventRecurrence.day2TimeDay(byday[i]));
565747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            days |= 1 << j;
566747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        }
567747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    }
568747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                }
569747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
570747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
571747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // BYMONTHDAY
572747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // Q: What happens if we have BYMONTHDAY and BYDAY?
573747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // A: I didn't see it in the spec, so in lieu of that, we'll
574747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // intersect the two.  That seems reasonable to me.
575747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            if (r.freq > EventRecurrence.WEEKLY) {
576747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                count = r.bymonthdayCount;
577747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                if (count != 0) {
578747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    bymonthday = r.bymonthday;
579747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    if (r.bydayCount == 0) {
580747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        for (i=0; i<count; i++) {
581747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            v = bymonthday[i];
582747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            if (v >= 0) {
583747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                days |= 1 << v;
584747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            } else {
585747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                j = lastDayThisMonth + v + 1; // v is negative
586747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                if (j >= 1 && j <= lastDayThisMonth) {
587747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                    days |= 1 << j;
588747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                }
589747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            }
590747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        }
591747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    } else {
592747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        // This is O(lastDayThisMonth*count), which is really
593747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        // O(count) with a decent sized constant.
594747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        for (j=1; j<=lastDayThisMonth; j++) {
595747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            next_day : {
596747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                if ((days&(1<<j)) != 0) {
597747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                    for (i=0; i<count; i++) {
598747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                        if (bymonthday[i] == j) {
599747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                            break next_day;
600747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                        }
601747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                    }
602747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                    days &= ~(1<<j);
603747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                }
604747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            }
605747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        }
606747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    }
607747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                }
608747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
609747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            return days;
610747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
611747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
612747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        private EventRecurrence mR;
613747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        private int mDays;
614747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        private Time mTime;
615747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        private int mYear;
616747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        private int mMonth;
617747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    }
618747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
619747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    /**
620747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * Expands the recurrence within the given range using the given dtstart
621747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * value. Returns an array of longs where each element is a date in UTC
622747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * milliseconds. The return value is never null.  If there are no dates
623747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * then an array of length zero is returned.
624747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     *
625747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @param dtstart a Time object representing the first occurrence
626747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @param recur the recurrence rules, including RRULE, RDATES, EXRULE, and
627747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * EXDATES
628747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @param rangeStartMillis the beginning of the range to expand, in UTC
629747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * milliseconds
630747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @param rangeEndMillis the non-inclusive end of the range to expand, in
631747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * UTC milliseconds; use -1 for the entire range.
632747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @return an array of dates, each date is in UTC milliseconds
633747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @throws DateException
634747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @throws android.util.TimeFormatException if recur cannot be parsed
635747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     */
636747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    public long[] expand(Time dtstart,
637747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            RecurrenceSet recur,
638747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            long rangeStartMillis,
639747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            long rangeEndMillis) throws DateException {
640747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        String timezone = dtstart.timezone;
641747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        mIterator.clear(timezone);
642747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        mGenerated.clear(timezone);
643747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
644747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        // We don't need to clear the mUntil (and it wouldn't do any good to
645747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        // do so) because the "until" date string is specified in UTC and that
646747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        // sets the timezone in the mUntil Time object.
647747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
648747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        mIterator.set(rangeStartMillis);
649747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        long rangeStartDateValue = normDateTimeComparisonValue(mIterator);
650747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
651747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        long rangeEndDateValue;
652747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        if (rangeEndMillis != -1) {
653747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            mIterator.set(rangeEndMillis);
654747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            rangeEndDateValue = normDateTimeComparisonValue(mIterator);
655747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        } else {
656747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            rangeEndDateValue = Long.MAX_VALUE;
657747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
658747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
659747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        TreeSet<Long> dtSet = new TreeSet<Long>();
660747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
661747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        if (recur.rrules != null) {
662747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            for (EventRecurrence rrule : recur.rrules) {
663747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                expand(dtstart, rrule, rangeStartDateValue,
664747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        rangeEndDateValue, true /* add */, dtSet);
665747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
666747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
667747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        if (recur.rdates != null) {
668747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            for (long dt : recur.rdates) {
669747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                // The dates are stored as milliseconds. We need to convert
670747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                // them to year/month/day values in the local timezone.
671747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                mIterator.set(dt);
672747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                long dtvalue = normDateTimeComparisonValue(mIterator);
673747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                dtSet.add(dtvalue);
674747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
675747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
676747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        if (recur.exrules != null) {
677747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            for (EventRecurrence exrule : recur.exrules) {
678747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                expand(dtstart, exrule, rangeStartDateValue,
679747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        rangeEndDateValue, false /* remove */, dtSet);
680747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
681747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
682747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        if (recur.exdates != null) {
683747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            for (long dt : recur.exdates) {
684747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                // The dates are stored as milliseconds. We need to convert
685747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                // them to year/month/day values in the local timezone.
686747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                mIterator.set(dt);
687747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                long dtvalue = normDateTimeComparisonValue(mIterator);
688747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                dtSet.remove(dtvalue);
689747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
690747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
691747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        if (dtSet.isEmpty()) {
692747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // this can happen if the recurrence does not occur within the
693747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // expansion window.
694747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            return new long[0];
695747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
696747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
697747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        // The values in dtSet are represented in a special form that is useful
698747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        // for fast comparisons and that is easy to generate from year/month/day
699747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        // values. We need to convert these to UTC milliseconds and also to
700747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        // ensure that the dates are valid.
701747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        int len = dtSet.size();
702747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        long[] dates = new long[len];
703747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        int i = 0;
704747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        for (Long val: dtSet) {
705747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            setTimeFromLongValue(mIterator, val);
706747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            dates[i++] = mIterator.toMillis(true /* ignore isDst */);
707747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
708747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        return dates;
709747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    }
710747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
711747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    /**
712747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * Run the recurrence algorithm.  Processes events defined in the local
713747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * timezone of the event.  Return a list of iCalendar DATETIME
714747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * strings containing the start date/times of the occurrences; the output
715747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * times are defined in the local timezone of the event.
716747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     *
717747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * If you want all of the events, pass Long.MAX_VALUE for rangeEndDateValue.  If you pass
718747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * Long.MAX_VALUE for rangeEnd, and the event doesn't have a COUNT or UNTIL field,
719747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * you'll get a DateException.
720747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     *
721747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @param dtstart the dtstart date as defined in RFC2445.  This
722747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * {@link Time} should be in the timezone of the event.
723747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @param r the parsed recurrence, as defiend in RFC2445
724747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @param rangeStartDateValue the first date-time you care about, inclusive
725747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @param rangeEndDateValue the last date-time you care about, not inclusive (so
726747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     *                  if you care about everything up through and including
727747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     *                  Dec 22 1995, set last to Dec 23, 1995 00:00:00
728747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @param add Whether or not we should add to out, or remove from out.
729747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @param out the TreeSet you'd like to fill with the events
730747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @throws DateException
731747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @throws android.util.TimeFormatException if r cannot be parsed.
732747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     */
733747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    public void expand(Time dtstart,
734747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            EventRecurrence r,
735747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            long rangeStartDateValue,
736747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            long rangeEndDateValue,
737747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            boolean add,
738747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            TreeSet<Long> out) throws DateException {
739747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        unsafeNormalize(dtstart);
740747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        long dtstartDateValue = normDateTimeComparisonValue(dtstart);
741747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        int count = 0;
742747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
743747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        // add the dtstart instance to the recurrence, if within range.
744747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        // For example, if dtstart is Mar 1, 2010 and the range is Jan 1 - Apr 1,
745747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        // then add it here and increment count.  If the range is earlier or later,
746747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        // then don't add it here.  In that case, count will be incremented later
747747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        // inside  the loop.  It is important that count gets incremented exactly
748747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        // once here or in the loop for dtstart.
749dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden        //
7500304a16e191a2e2af8289c6e7e1ad9734a0dc06dAndy McFadden        // NOTE: if DTSTART is not synchronized with the recurrence rule, the first instance
7510304a16e191a2e2af8289c6e7e1ad9734a0dc06dAndy McFadden        //       we return will not fit the RRULE pattern.
752747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        if (add && dtstartDateValue >= rangeStartDateValue
753747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                && dtstartDateValue < rangeEndDateValue) {
754747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            out.add(dtstartDateValue);
755747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            ++count;
756747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
757747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
758747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        Time iterator = mIterator;
759747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        Time until = mUntil;
760747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        StringBuilder sb = mStringBuilder;
761747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        Time generated = mGenerated;
762747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        DaySet days = mDays;
763747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
764747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        try {
765747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
766747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            days.setRecurrence(r);
767747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            if (rangeEndDateValue == Long.MAX_VALUE && r.until == null && r.count == 0) {
768747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                throw new DateException(
769747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        "No range end provided for a recurrence that has no UNTIL or COUNT.");
770747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
771747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
772747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // the top-level frequency
773747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            int freqField;
774747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            int freqAmount = r.interval;
775747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            int freq = r.freq;
776747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            switch (freq)
777747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            {
778747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                case EventRecurrence.SECONDLY:
779747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    freqField = Time.SECOND;
780747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    break;
781747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                case EventRecurrence.MINUTELY:
782747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    freqField = Time.MINUTE;
783747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    break;
784747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                case EventRecurrence.HOURLY:
785747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    freqField = Time.HOUR;
786747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    break;
787747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                case EventRecurrence.DAILY:
788747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    freqField = Time.MONTH_DAY;
789747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    break;
790747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                case EventRecurrence.WEEKLY:
791747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    freqField = Time.MONTH_DAY;
792747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    freqAmount = 7 * r.interval;
793747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    if (freqAmount <= 0) {
794747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        freqAmount = 7;
795747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    }
796747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    break;
797747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                case EventRecurrence.MONTHLY:
798747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    freqField = Time.MONTH;
799747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    break;
800747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                case EventRecurrence.YEARLY:
801747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    freqField = Time.YEAR;
802747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    break;
803747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                default:
804747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    throw new DateException("bad freq=" + freq);
805747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
806747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            if (freqAmount <= 0) {
807747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                freqAmount = 1;
808747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
809747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
810747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            int bymonthCount = r.bymonthCount;
811747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            boolean usebymonth = useBYX(freq, EventRecurrence.MONTHLY, bymonthCount);
812747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            boolean useDays = freq >= EventRecurrence.WEEKLY &&
813747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                 (r.bydayCount > 0 || r.bymonthdayCount > 0);
814747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            int byhourCount = r.byhourCount;
815747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            boolean usebyhour = useBYX(freq, EventRecurrence.HOURLY, byhourCount);
816747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            int byminuteCount = r.byminuteCount;
817747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            boolean usebyminute = useBYX(freq, EventRecurrence.MINUTELY, byminuteCount);
818747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            int bysecondCount = r.bysecondCount;
819747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            boolean usebysecond = useBYX(freq, EventRecurrence.SECONDLY, bysecondCount);
820747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
821747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // initialize the iterator
822747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            iterator.set(dtstart);
823747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            if (freqField == Time.MONTH) {
824747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                if (useDays) {
825747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    // if it's monthly, and we're going to be generating
826747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    // days, set the iterator day field to 1 because sometimes
827747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    // we'll skip months if it's greater than 28.
828747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    // XXX Do we generate days for MONTHLY w/ BYHOUR?  If so,
829747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    // we need to do this then too.
830747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    iterator.monthDay = 1;
831747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                }
832747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
833747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
834747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            long untilDateValue;
835747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            if (r.until != null) {
836747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                // Ensure that the "until" date string is specified in UTC.
837747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                String untilStr = r.until;
838747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                // 15 is length of date-time without trailing Z e.g. "20090204T075959"
839747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                // A string such as 20090204 is a valid UNTIL (see RFC 2445) and the
840747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                // Z should not be added.
841747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                if (untilStr.length() == 15) {
842747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    untilStr = untilStr + 'Z';
843747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                }
844747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                // The parse() method will set the timezone to UTC
845747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                until.parse(untilStr);
846747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
847747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                // We need the "until" year/month/day values to be in the same
848747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                // timezone as all the generated dates so that we can compare them
849747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                // using the values returned by normDateTimeComparisonValue().
850747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                until.switchTimezone(dtstart.timezone);
851747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                untilDateValue = normDateTimeComparisonValue(until);
852747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            } else {
853747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                untilDateValue = Long.MAX_VALUE;
854747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
855747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
856747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            sb.ensureCapacity(15);
857747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            sb.setLength(15); // TODO: pay attention to whether or not the event
858747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // is an all-day one.
859747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
860747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            if (SPEW) {
861747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                Log.i(TAG, "expand called w/ rangeStart=" + rangeStartDateValue
862747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        + " rangeEnd=" + rangeEndDateValue);
863747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
864747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
865747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // go until the end of the range or we're done with this event
866747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            boolean eventEnded = false;
867747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            int failsafe = 0; // Avoid infinite loops
868747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            events: {
869747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                while (true) {
870747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    int monthIndex = 0;
871747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    if (failsafe++ > MAX_ALLOWED_ITERATIONS) { // Give up after about 1 second of processing
872207e297d6911151d3e556be23ede3649beccb674Fredrik Hellén-Halme                        Log.w(TAG, "Recurrence processing stuck with r=" + r + " rangeStart="
873207e297d6911151d3e556be23ede3649beccb674Fredrik Hellén-Halme                                  + rangeStartDateValue + " rangeEnd=" + rangeEndDateValue);
874207e297d6911151d3e556be23ede3649beccb674Fredrik Hellén-Halme                        break;
875747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    }
876747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
877747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    unsafeNormalize(iterator);
878747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
879747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    int iteratorYear = iterator.year;
880747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    int iteratorMonth = iterator.month + 1;
881747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    int iteratorDay = iterator.monthDay;
882747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    int iteratorHour = iterator.hour;
883747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    int iteratorMinute = iterator.minute;
884747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    int iteratorSecond = iterator.second;
885747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
886747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    // year is never expanded -- there is no BYYEAR
887747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    generated.set(iterator);
888747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
889747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    if (SPEW) Log.i(TAG, "year=" + generated.year);
890747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
891747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    do { // month
892747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        int month = usebymonth
893747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                        ? r.bymonth[monthIndex]
894747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                        : iteratorMonth;
895747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        month--;
896747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        if (SPEW) Log.i(TAG, "  month=" + month);
897747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
898747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        int dayIndex = 1;
899747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        int lastDayToExamine = 0;
900747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
901747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        // Use this to handle weeks that overlap the end of the month.
902747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        // Keep the year and month that days is for, and generate it
903747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        // when needed in the loop
904747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        if (useDays) {
905747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            // Determine where to start and end, don't worry if this happens
906747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            // to be before dtstart or after the end, because that will be
907747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            // filtered in the inner loop
908747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            if (freq == EventRecurrence.WEEKLY) {
9099a91eb9d6c3c28b54223dae453c9d456b0c87355Andy McFadden                                /*
9109a91eb9d6c3c28b54223dae453c9d456b0c87355Andy McFadden                                 * iterator.weekDay indicates the day of the week (0-6, SU-SA).
9119a91eb9d6c3c28b54223dae453c9d456b0c87355Andy McFadden                                 * Because dayIndex might start in the middle of a week, and we're
9129a91eb9d6c3c28b54223dae453c9d456b0c87355Andy McFadden                                 * interested in treating a week as a unit, we want to move
9139a91eb9d6c3c28b54223dae453c9d456b0c87355Andy McFadden                                 * backward to the start of the week.  (This could make the
9149a91eb9d6c3c28b54223dae453c9d456b0c87355Andy McFadden                                 * dayIndex negative, which will be corrected by normalization
9159a91eb9d6c3c28b54223dae453c9d456b0c87355Andy McFadden                                 * later on.)
9169a91eb9d6c3c28b54223dae453c9d456b0c87355Andy McFadden                                 *
9179a91eb9d6c3c28b54223dae453c9d456b0c87355Andy McFadden                                 * The day that starts the week is determined by WKST, which
9189a91eb9d6c3c28b54223dae453c9d456b0c87355Andy McFadden                                 * defaults to MO.
9199a91eb9d6c3c28b54223dae453c9d456b0c87355Andy McFadden                                 *
9209a91eb9d6c3c28b54223dae453c9d456b0c87355Andy McFadden                                 * Example: dayIndex is Tuesday the 8th, and weeks start on
9219a91eb9d6c3c28b54223dae453c9d456b0c87355Andy McFadden                                 * Thursdays.  Tuesday is day 2, Thursday is day 4, so we
9229a91eb9d6c3c28b54223dae453c9d456b0c87355Andy McFadden                                 * want to move back (2 - 4 + 7) % 7 = 5 days to the previous
9239a91eb9d6c3c28b54223dae453c9d456b0c87355Andy McFadden                                 * Thursday.  If weeks started on Mondays, we would only
9249a91eb9d6c3c28b54223dae453c9d456b0c87355Andy McFadden                                 * need to move back (2 - 1 + 7) % 7 = 1 day.
9259a91eb9d6c3c28b54223dae453c9d456b0c87355Andy McFadden                                 */
9269a91eb9d6c3c28b54223dae453c9d456b0c87355Andy McFadden                                int weekStartAdj = (iterator.weekDay -
9279a91eb9d6c3c28b54223dae453c9d456b0c87355Andy McFadden                                        EventRecurrence.day2TimeDay(r.wkst) + 7) % 7;
9289a91eb9d6c3c28b54223dae453c9d456b0c87355Andy McFadden                                dayIndex = iterator.monthDay - weekStartAdj;
929747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                lastDayToExamine = dayIndex + 6;
930747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            } else {
931747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                lastDayToExamine = generated
932747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                    .getActualMaximum(Time.MONTH_DAY);
933747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            }
934747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            if (SPEW) Log.i(TAG, "dayIndex=" + dayIndex
935747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                    + " lastDayToExamine=" + lastDayToExamine
936747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                    + " days=" + days);
937747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        }
938747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
939747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        do { // day
940747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            int day;
941747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            if (useDays) {
942747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                if (!days.get(iterator, dayIndex)) {
943747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                    dayIndex++;
944747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                    continue;
945747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                } else {
946747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                    day = dayIndex;
947747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                }
948747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            } else {
949747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                day = iteratorDay;
950747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            }
951747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            if (SPEW) Log.i(TAG, "    day=" + day);
952747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
953747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            // hour
954747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            int hourIndex = 0;
955747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            do {
956747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                int hour = usebyhour
957747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                ? r.byhour[hourIndex]
958747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                : iteratorHour;
959747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                if (SPEW) Log.i(TAG, "      hour=" + hour + " usebyhour=" + usebyhour);
960747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
961747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                // minute
962747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                int minuteIndex = 0;
963747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                do {
964747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                    int minute = usebyminute
965747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                    ? r.byminute[minuteIndex]
966747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                    : iteratorMinute;
967747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                    if (SPEW) Log.i(TAG, "        minute=" + minute);
968747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
969747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                    // second
970747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                    int secondIndex = 0;
971747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                    do {
972747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                        int second = usebysecond
973747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                        ? r.bysecond[secondIndex]
974747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                        : iteratorSecond;
975747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                        if (SPEW) Log.i(TAG, "          second=" + second);
976747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
977747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                        // we do this here each time, because if we distribute it, we find the
978747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                        // month advancing extra times, as we set the month to the 32nd, 33rd, etc.
979747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                        // days.
980747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                        generated.set(second, minute, hour, day, month, iteratorYear);
981747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                        unsafeNormalize(generated);
982747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
983747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                        long genDateValue = normDateTimeComparisonValue(generated);
984747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                        // sometimes events get generated (BYDAY, BYHOUR, etc.) that
985747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                        // are before dtstart.  Filter these.  I believe this is correct,
986747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                        // but Google Calendar doesn't seem to always do this.
987747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                        if (genDateValue >= dtstartDateValue) {
988747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                            // filter and then add
989dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                                            // TODO: we don't check for stop conditions (like
990dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                                            //       passing the "end" date) unless the filter
991dce6a0a89883ab3e76212a11a5a889407887718fAndy McFadden                                            //       allows the event.  Could stop sooner.
992747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                            int filtered = filter(r, generated);
993747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                            if (0 == filtered) {
994747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
995747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                // increase the count as long
996747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                // as this isn't the same
997747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                // as the first instance
998747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                // specified by the DTSTART
999747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                // (for RRULEs -- additive).
1000747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                // This condition must be the complement of the
1001747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                // condition for incrementing count at the
1002747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                // beginning of the method, so if we don't
1003747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                // increment count there, we increment it here.
1004747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                // For example, if add is set and dtstartDateValue
1005747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                // is inside the start/end range, then it was added
1006747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                // and count was incremented at the beginning.
1007747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                // If dtstartDateValue is outside the range or add
1008747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                // is not set, then we must increment count here.
1009747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                if (!(dtstartDateValue == genDateValue
1010747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                        && add
1011747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                        && dtstartDateValue >= rangeStartDateValue
1012747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                        && dtstartDateValue < rangeEndDateValue)) {
1013747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                    ++count;
1014747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                }
1015747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                // one reason we can stop is that
1016747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                // we're past the until date
1017747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                if (genDateValue > untilDateValue) {
1018747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                    if (SPEW) {
1019747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                        Log.i(TAG, "stopping b/c until="
1020747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                            + untilDateValue
1021747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                            + " generated="
1022747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                            + genDateValue);
1023747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                    }
1024747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                    break events;
1025747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                }
1026747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                // or we're past rangeEnd
1027747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                if (genDateValue >= rangeEndDateValue) {
1028747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                    if (SPEW) {
1029747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                        Log.i(TAG, "stopping b/c rangeEnd="
1030747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                                + rangeEndDateValue
1031747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                                + " generated=" + generated);
1032747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                    }
1033747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                    break events;
1034747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                }
1035747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
1036747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                if (genDateValue >= rangeStartDateValue) {
1037747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                    if (SPEW) {
1038747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                        Log.i(TAG, "adding date=" + generated + " filtered=" + filtered);
1039747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                    }
1040747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                    if (add) {
1041747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                        out.add(genDateValue);
1042747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                    } else {
1043747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                        out.remove(genDateValue);
1044747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                    }
1045747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                }
1046747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                // another is that count is high enough
1047747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                if (r.count > 0 && r.count == count) {
1048747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                    //Log.i(TAG, "stopping b/c count=" + count);
1049747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                    break events;
1050747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                                }
1051747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                            }
1052747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                        }
1053747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                        secondIndex++;
1054747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                    } while (usebysecond && secondIndex < bysecondCount);
1055747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                    minuteIndex++;
1056747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                } while (usebyminute && minuteIndex < byminuteCount);
1057747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                hourIndex++;
1058747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            } while (usebyhour && hourIndex < byhourCount);
1059747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            dayIndex++;
1060747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        } while (useDays && dayIndex <= lastDayToExamine);
1061747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        monthIndex++;
1062747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    } while (usebymonth && monthIndex < bymonthCount);
1063747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
1064747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    // Add freqAmount to freqField until we get another date that we want.
1065747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    // We don't want to "generate" dates with the iterator.
1066747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    // XXX: We do this for days, because there is a varying number of days
1067747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    // per month
1068747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    int oldDay = iterator.monthDay;
1069747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    generated.set(iterator);  // just using generated as a temporary.
1070747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    int n = 1;
1071747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    while (true) {
1072747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        int value = freqAmount * n;
1073747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        switch (freqField) {
1074747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            case Time.SECOND:
1075747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                iterator.second += value;
1076747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                break;
1077747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            case Time.MINUTE:
1078747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                iterator.minute += value;
1079747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                break;
1080747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            case Time.HOUR:
1081747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                iterator.hour += value;
1082747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                break;
1083747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            case Time.MONTH_DAY:
1084747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                iterator.monthDay += value;
1085747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                break;
1086747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            case Time.MONTH:
1087747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                iterator.month += value;
1088747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                break;
1089747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            case Time.YEAR:
1090747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                iterator.year += value;
1091747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                break;
1092747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            case Time.WEEK_DAY:
1093747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                iterator.monthDay += value;
1094747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                break;
1095747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            case Time.YEAR_DAY:
1096747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                iterator.monthDay += value;
1097747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                break;
1098747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            default:
1099747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                                throw new RuntimeException("bad field=" + freqField);
1100747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        }
1101747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
1102747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        unsafeNormalize(iterator);
1103747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        if (freqField != Time.YEAR && freqField != Time.MONTH) {
1104747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            break;
1105747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        }
1106747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        if (iterator.monthDay == oldDay) {
1107747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                            break;
1108747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        }
1109747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        n++;
1110747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                        iterator.set(generated);
1111747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    }
1112747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                }
1113747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
1114747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
1115747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        catch (DateException e) {
1116747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            Log.w(TAG, "DateException with r=" + r + " rangeStart=" + rangeStartDateValue
1117747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    + " rangeEnd=" + rangeEndDateValue);
1118747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            throw e;
1119747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
1120747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        catch (RuntimeException t) {
1121747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            Log.w(TAG, "RuntimeException with r=" + r + " rangeStart=" + rangeStartDateValue
1122747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    + " rangeEnd=" + rangeEndDateValue);
1123747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            throw t;
1124747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
1125747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    }
1126747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
1127747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    /**
1128747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * Normalizes the date fields to give a valid date, but if the time falls
1129747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * in the invalid window during a transition out of Daylight Saving Time
1130747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * when time jumps forward an hour, then the "normalized" value will be
1131747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * invalid.
1132747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * <p>
1133747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * This method also computes the weekDay and yearDay fields.
1134747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     *
1135747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * <p>
1136747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * This method does not modify the fields isDst, or gmtOff.
1137747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     */
1138747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    static void unsafeNormalize(Time date) {
1139747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        int second = date.second;
1140747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        int minute = date.minute;
1141747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        int hour = date.hour;
1142747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        int monthDay = date.monthDay;
1143747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        int month = date.month;
1144747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        int year = date.year;
1145747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
1146747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        int addMinutes = ((second < 0) ? (second - 59) : second) / 60;
1147747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        second -= addMinutes * 60;
1148747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        minute += addMinutes;
1149747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        int addHours = ((minute < 0) ? (minute - 59) : minute) / 60;
1150747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        minute -= addHours * 60;
1151747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        hour += addHours;
1152747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        int addDays = ((hour < 0) ? (hour - 23) : hour) / 24;
1153747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        hour -= addDays * 24;
1154747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        monthDay += addDays;
1155747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
1156747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        // We want to make "monthDay" positive. We do this by subtracting one
1157747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        // from the year and adding a year's worth of days to "monthDay" in
1158747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        // the following loop while "monthDay" <= 0.
1159747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        while (monthDay <= 0) {
1160747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // If month is after Feb, then add this year's length so that we
1161747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // include this year's leap day, if any.
1162747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // Otherwise (the month is Feb or earlier), add last year's length.
1163747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // Subtract one from the year in either case. This gives the same
1164747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // effective date but makes monthDay (the day of the month) much
1165747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // larger. Eventually (usually in one iteration) monthDay will
1166747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // be positive.
1167747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            int days = month > 1 ? yearLength(year) : yearLength(year - 1);
1168747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            monthDay += days;
1169747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            year -= 1;
1170747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
1171747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        // At this point, monthDay >= 1. Normalize the month to the range [0,11].
1172747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        if (month < 0) {
1173747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            int years = (month + 1) / 12 - 1;
1174747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            year += years;
1175747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            month -= 12 * years;
1176747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        } else if (month >= 12) {
1177747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            int years = month / 12;
1178747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            year += years;
1179747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            month -= 12 * years;
1180747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
1181747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        // At this point, month is in the range [0,11] and monthDay >= 1.
1182747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        // Now loop until the monthDay is in the correct range for the month.
1183747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        while (true) {
1184747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            // On January, check if we can jump forward a whole year.
1185747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            if (month == 0) {
1186747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                int yearLength = yearLength(year);
1187747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                if (monthDay > yearLength) {
1188747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    year++;
1189747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    monthDay -= yearLength;
1190747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                }
1191747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            }
1192747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            int monthLength = monthLength(year, month);
1193747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            if (monthDay > monthLength) {
1194747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                monthDay -= monthLength;
1195747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                month++;
1196747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                if (month >= 12) {
1197747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    month -= 12;
1198747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                    year++;
1199747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                }
1200747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            } else break;
1201747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
1202747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        // At this point, monthDay <= the length of the current month and is
1203747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        // in the range [1,31].
1204747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
1205747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        date.second = second;
1206747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        date.minute = minute;
1207747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        date.hour = hour;
1208747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        date.monthDay = monthDay;
1209747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        date.month = month;
1210747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        date.year = year;
1211747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        date.weekDay = weekDay(year, month, monthDay);
1212747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        date.yearDay = yearDay(year, month, monthDay);
1213747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    }
1214747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
1215747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    /**
1216747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * Returns true if the given year is a leap year.
1217747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     *
1218747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @param year the given year to test
1219747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @return true if the given year is a leap year.
1220747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     */
1221747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    static boolean isLeapYear(int year) {
1222747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        return (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0));
1223747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    }
1224747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
1225747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    /**
1226747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * Returns the number of days in the given year.
1227747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     *
1228747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @param year the given year
1229747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @return the number of days in the given year.
1230747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     */
1231747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    static int yearLength(int year) {
1232747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        return isLeapYear(year) ? 366 : 365;
1233747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    }
1234747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
1235747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    private static final int[] DAYS_PER_MONTH = { 31, 28, 31, 30, 31, 30, 31,
1236747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            31, 30, 31, 30, 31 };
1237747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    private static final int[] DAYS_IN_YEAR_PRECEDING_MONTH = { 0, 31, 59, 90,
1238747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        120, 151, 180, 212, 243, 273, 304, 334 };
1239747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
1240747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    /**
1241747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * Returns the number of days in the given month of the given year.
1242747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     *
1243747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @param year the given year.
1244747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @param month the given month in the range [0,11]
1245747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @return the number of days in the given month of the given year.
1246747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     */
1247747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    static int monthLength(int year, int month) {
1248747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        int n = DAYS_PER_MONTH[month];
1249747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        if (n != 28) {
1250747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            return n;
1251747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
1252747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        return isLeapYear(year) ? 29 : 28;
1253747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    }
1254747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
1255747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    /**
1256747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * Computes the weekday, a number in the range [0,6] where Sunday=0, from
1257747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * the given year, month, and day.
1258747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     *
1259747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @param year the year
1260747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @param month the 0-based month in the range [0,11]
1261747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @param day the 1-based day of the month in the range [1,31]
1262747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @return the weekday, a number in the range [0,6] where Sunday=0
1263747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     */
1264747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    static int weekDay(int year, int month, int day) {
1265747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        if (month <= 1) {
1266747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            month += 12;
1267747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            year -= 1;
1268747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
1269747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        return (day + (13 * month - 14) / 5 + year + year/4 - year/100 + year/400) % 7;
1270747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    }
1271747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
1272747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    /**
1273747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * Computes the 0-based "year day", given the year, month, and day.
1274747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     *
1275747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @param year the year
1276747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @param month the 0-based month in the range [0,11]
1277747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @param day the 1-based day in the range [1,31]
1278747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @return the 0-based "year day", the number of days into the year
1279747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     */
1280747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    static int yearDay(int year, int month, int day) {
1281747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        int yearDay = DAYS_IN_YEAR_PRECEDING_MONTH[month] + day - 1;
1282747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        if (month >= 2 && isLeapYear(year)) {
1283747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden            yearDay += 1;
1284747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        }
1285747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        return yearDay;
1286747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    }
1287747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
1288747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    /**
1289747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * Converts a normalized Time value to a 64-bit long. The mapping of Time
1290747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * values to longs provides a total ordering on the Time values so that
1291747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * two Time values can be compared efficiently by comparing their 64-bit
1292747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * long values.  This is faster than converting the Time values to UTC
1293747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * millliseconds.
1294747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     *
1295747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @param normalized a Time object whose date and time fields have been
1296747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * normalized
1297747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * @return a 64-bit long value that can be used for comparing and ordering
1298747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     * dates and times represented by Time objects
1299747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden     */
1300747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    private static final long normDateTimeComparisonValue(Time normalized) {
1301747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        // 37 bits for the year, 4 bits for the month, 5 bits for the monthDay,
1302747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        // 5 bits for the hour, 6 bits for the minute, 6 bits for the second.
1303747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        return ((long)normalized.year << 26) + (normalized.month << 22)
1304747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                + (normalized.monthDay << 17) + (normalized.hour << 12)
1305747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden                + (normalized.minute << 6) + normalized.second;
1306747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    }
1307747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden
1308747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    private static final void setTimeFromLongValue(Time date, long val) {
1309747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        date.year = (int) (val >> 26);
1310747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        date.month = (int) (val >> 22) & 0xf;
1311747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        date.monthDay = (int) (val >> 17) & 0x1f;
1312747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        date.hour = (int) (val >> 12) & 0x1f;
1313747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        date.minute = (int) (val >> 6) & 0x3f;
1314747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden        date.second = (int) (val & 0x3f);
1315747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden    }
1316747abc3833aec07827fa6b831e58f78e72c139d1Andy McFadden}
1317