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