10d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden/*
20d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Copyright (C) 2006 The Android Open Source Project
30d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden *
40d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Licensed under the Apache License, Version 2.0 (the "License");
50d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * you may not use this file except in compliance with the License.
60d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * You may obtain a copy of the License at
70d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden *
80d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden *      http://www.apache.org/licenses/LICENSE-2.0
90d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden *
100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Unless required by applicable law or agreed to in writing, software
110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * distributed under the License is distributed on an "AS IS" BASIS,
120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * See the License for the specific language governing permissions and
140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * limitations under the License.
150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */
160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
1706b3293d5af3454a39681cfd659271551354b8a0Michael Chanpackage com.android.calendarcommon2;
180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport android.text.TextUtils;
200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport android.text.format.Time;
210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport android.util.Log;
220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport android.util.TimeFormatException;
230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport java.util.Calendar;
250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport java.util.HashMap;
260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden/**
280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Event recurrence utility functions.
290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */
300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenpublic class EventRecurrence {
310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static String TAG = "EventRecur";
320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public static final int SECONDLY = 1;
340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public static final int MINUTELY = 2;
350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public static final int HOURLY = 3;
360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public static final int DAILY = 4;
370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public static final int WEEKLY = 5;
380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public static final int MONTHLY = 6;
390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public static final int YEARLY = 7;
400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public static final int SU = 0x00010000;
420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public static final int MO = 0x00020000;
430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public static final int TU = 0x00040000;
440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public static final int WE = 0x00080000;
450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public static final int TH = 0x00100000;
460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public static final int FR = 0x00200000;
470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public static final int SA = 0x00400000;
480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public Time      startDate;     // set by setStartDate(), not parse()
500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public int       freq;          // SECONDLY, MINUTELY, etc.
520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public String    until;
530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public int       count;
540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public int       interval;
550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public int       wkst;          // SU, MO, TU, etc.
560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /* lists with zero entries may be null references */
580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public int[]     bysecond;
590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public int       bysecondCount;
600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public int[]     byminute;
610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public int       byminuteCount;
620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public int[]     byhour;
630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public int       byhourCount;
640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public int[]     byday;
650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public int[]     bydayNum;
660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public int       bydayCount;
670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public int[]     bymonthday;
680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public int       bymonthdayCount;
690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public int[]     byyearday;
700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public int       byyeardayCount;
710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public int[]     byweekno;
720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public int       byweeknoCount;
730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public int[]     bymonth;
740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public int       bymonthCount;
750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public int[]     bysetpos;
760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public int       bysetposCount;
770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /** maps a part string to a parser object */
790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static HashMap<String,PartParser> sParsePartMap;
800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    static {
810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        sParsePartMap = new HashMap<String,PartParser>();
820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        sParsePartMap.put("FREQ", new ParseFreq());
830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        sParsePartMap.put("UNTIL", new ParseUntil());
840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        sParsePartMap.put("COUNT", new ParseCount());
850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        sParsePartMap.put("INTERVAL", new ParseInterval());
860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        sParsePartMap.put("BYSECOND", new ParseBySecond());
870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        sParsePartMap.put("BYMINUTE", new ParseByMinute());
880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        sParsePartMap.put("BYHOUR", new ParseByHour());
890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        sParsePartMap.put("BYDAY", new ParseByDay());
900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        sParsePartMap.put("BYMONTHDAY", new ParseByMonthDay());
910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        sParsePartMap.put("BYYEARDAY", new ParseByYearDay());
920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        sParsePartMap.put("BYWEEKNO", new ParseByWeekNo());
930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        sParsePartMap.put("BYMONTH", new ParseByMonth());
940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        sParsePartMap.put("BYSETPOS", new ParseBySetPos());
950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        sParsePartMap.put("WKST", new ParseWkst());
960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
980d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /* values for bit vector that keeps track of what we have already seen */
990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static final int PARSED_FREQ = 1 << 0;
1000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static final int PARSED_UNTIL = 1 << 1;
1010d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static final int PARSED_COUNT = 1 << 2;
1020d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static final int PARSED_INTERVAL = 1 << 3;
1030d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static final int PARSED_BYSECOND = 1 << 4;
1040d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static final int PARSED_BYMINUTE = 1 << 5;
1050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static final int PARSED_BYHOUR = 1 << 6;
1060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static final int PARSED_BYDAY = 1 << 7;
1070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static final int PARSED_BYMONTHDAY = 1 << 8;
1080d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static final int PARSED_BYYEARDAY = 1 << 9;
1090d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static final int PARSED_BYWEEKNO = 1 << 10;
1100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static final int PARSED_BYMONTH = 1 << 11;
1110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static final int PARSED_BYSETPOS = 1 << 12;
1120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static final int PARSED_WKST = 1 << 13;
1130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
1140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /** maps a FREQ value to an integer constant */
1150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static final HashMap<String,Integer> sParseFreqMap = new HashMap<String,Integer>();
1160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    static {
1170d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        sParseFreqMap.put("SECONDLY", SECONDLY);
1180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        sParseFreqMap.put("MINUTELY", MINUTELY);
1190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        sParseFreqMap.put("HOURLY", HOURLY);
1200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        sParseFreqMap.put("DAILY", DAILY);
1210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        sParseFreqMap.put("WEEKLY", WEEKLY);
1220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        sParseFreqMap.put("MONTHLY", MONTHLY);
1230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        sParseFreqMap.put("YEARLY", YEARLY);
1240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
1250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
1260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /** maps a two-character weekday string to an integer constant */
1270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static final HashMap<String,Integer> sParseWeekdayMap = new HashMap<String,Integer>();
1280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    static {
1290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        sParseWeekdayMap.put("SU", SU);
1300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        sParseWeekdayMap.put("MO", MO);
1310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        sParseWeekdayMap.put("TU", TU);
1320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        sParseWeekdayMap.put("WE", WE);
1330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        sParseWeekdayMap.put("TH", TH);
1340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        sParseWeekdayMap.put("FR", FR);
1350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        sParseWeekdayMap.put("SA", SA);
1360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
1370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
1380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /** If set, allow lower-case recurrence rule strings.  Minor performance impact. */
139996f12565f11ea0130cbdc570c0853ae53073f18Michael Chan    private static final boolean ALLOW_LOWER_CASE = true;
1400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
1410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /** If set, validate the value of UNTIL parts.  Minor performance impact. */
1420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static final boolean VALIDATE_UNTIL = false;
1430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
1440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /** If set, require that only one of {UNTIL,COUNT} is present.  Breaks compat w/ old parser. */
1450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static final boolean ONLY_ONE_UNTIL_COUNT = false;
1460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
1470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
1480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /**
1490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * Thrown when a recurrence string provided can not be parsed according
1500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * to RFC2445.
1510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     */
1520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public static class InvalidFormatException extends RuntimeException {
1530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        InvalidFormatException(String s) {
1540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            super(s);
1550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
1560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
1570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
1580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
1590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public void setStartDate(Time date) {
1600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        startDate = date;
1610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
1620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
1630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /**
1640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * Converts one of the Calendar.SUNDAY constants to the SU, MO, etc.
1650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * constants.  btw, I think we should switch to those here too, to
1660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * get rid of this function, if possible.
1670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     */
1680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public static int calendarDay2Day(int day)
1690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    {
1700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        switch (day)
1710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        {
1720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case Calendar.SUNDAY:
1730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return SU;
1740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case Calendar.MONDAY:
1750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return MO;
1760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case Calendar.TUESDAY:
1770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return TU;
1780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case Calendar.WEDNESDAY:
1790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return WE;
1800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case Calendar.THURSDAY:
1810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return TH;
1820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case Calendar.FRIDAY:
1830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return FR;
1840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case Calendar.SATURDAY:
1850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return SA;
1860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            default:
1870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                throw new RuntimeException("bad day of week: " + day);
1880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
1890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
1900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
1910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public static int timeDay2Day(int day)
1920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    {
1930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        switch (day)
1940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        {
1950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case Time.SUNDAY:
1960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return SU;
1970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case Time.MONDAY:
1980d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return MO;
1990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case Time.TUESDAY:
2000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return TU;
2010d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case Time.WEDNESDAY:
2020d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return WE;
2030d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case Time.THURSDAY:
2040d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return TH;
2050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case Time.FRIDAY:
2060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return FR;
2070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case Time.SATURDAY:
2080d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return SA;
2090d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            default:
2100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                throw new RuntimeException("bad day of week: " + day);
2110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
2120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
2130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public static int day2TimeDay(int day)
2140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    {
2150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        switch (day)
2160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        {
2170d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case SU:
2180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return Time.SUNDAY;
2190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case MO:
2200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return Time.MONDAY;
2210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case TU:
2220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return Time.TUESDAY;
2230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case WE:
2240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return Time.WEDNESDAY;
2250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case TH:
2260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return Time.THURSDAY;
2270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case FR:
2280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return Time.FRIDAY;
2290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case SA:
2300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return Time.SATURDAY;
2310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            default:
2320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                throw new RuntimeException("bad day of week: " + day);
2330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
2340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
2350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
2360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /**
2370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * Converts one of the SU, MO, etc. constants to the Calendar.SUNDAY
2380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * constants.  btw, I think we should switch to those here too, to
2390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * get rid of this function, if possible.
2400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     */
2410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public static int day2CalendarDay(int day)
2420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    {
2430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        switch (day)
2440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        {
2450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case SU:
2460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return Calendar.SUNDAY;
2470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case MO:
2480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return Calendar.MONDAY;
2490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case TU:
2500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return Calendar.TUESDAY;
2510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case WE:
2520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return Calendar.WEDNESDAY;
2530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case TH:
2540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return Calendar.THURSDAY;
2550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case FR:
2560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return Calendar.FRIDAY;
2570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case SA:
2580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return Calendar.SATURDAY;
2590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            default:
2600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                throw new RuntimeException("bad day of week: " + day);
2610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
2620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
2630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
2640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /**
2650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * Converts one of the internal day constants (SU, MO, etc.) to the
2660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * two-letter string representing that constant.
2670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     *
2680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * @param day one the internal constants SU, MO, etc.
2690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * @return the two-letter string for the day ("SU", "MO", etc.)
2700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     *
2710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * @throws IllegalArgumentException Thrown if the day argument is not one of
2720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * the defined day constants.
2730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     */
2740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static String day2String(int day) {
2750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        switch (day) {
2760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        case SU:
2770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return "SU";
2780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        case MO:
2790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return "MO";
2800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        case TU:
2810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return "TU";
2820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        case WE:
2830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return "WE";
2840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        case TH:
2850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return "TH";
2860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        case FR:
2870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return "FR";
2880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        case SA:
2890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return "SA";
2900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        default:
2910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            throw new IllegalArgumentException("bad day argument: " + day);
2920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
2930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
2940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
2950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static void appendNumbers(StringBuilder s, String label,
2960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                                        int count, int[] values)
2970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    {
2980d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (count > 0) {
2990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            s.append(label);
3000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            count--;
3010d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            for (int i=0; i<count; i++) {
3020d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                s.append(values[i]);
3030d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                s.append(",");
3040d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            }
3050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            s.append(values[count]);
3060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
3070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
3080d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
3090d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private void appendByDay(StringBuilder s, int i)
3100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    {
3110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        int n = this.bydayNum[i];
3120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (n != 0) {
3130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            s.append(n);
3140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
3150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
3160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        String str = day2String(this.byday[i]);
3170d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        s.append(str);
3180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
3190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
3200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    @Override
3210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public String toString()
3220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    {
3230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        StringBuilder s = new StringBuilder();
3240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
3250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        s.append("FREQ=");
3260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        switch (this.freq)
3270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        {
3280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case SECONDLY:
3290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                s.append("SECONDLY");
3300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                break;
3310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case MINUTELY:
3320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                s.append("MINUTELY");
3330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                break;
3340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case HOURLY:
3350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                s.append("HOURLY");
3360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                break;
3370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case DAILY:
3380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                s.append("DAILY");
3390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                break;
3400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case WEEKLY:
3410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                s.append("WEEKLY");
3420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                break;
3430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case MONTHLY:
3440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                s.append("MONTHLY");
3450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                break;
3460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            case YEARLY:
3470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                s.append("YEARLY");
3480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                break;
3490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
3500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
3510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (!TextUtils.isEmpty(this.until)) {
3520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            s.append(";UNTIL=");
3530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            s.append(until);
3540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
3550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
3560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (this.count != 0) {
3570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            s.append(";COUNT=");
3580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            s.append(this.count);
3590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
3600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
3610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (this.interval != 0) {
3620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            s.append(";INTERVAL=");
3630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            s.append(this.interval);
3640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
3650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
3660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (this.wkst != 0) {
3670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            s.append(";WKST=");
3680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            s.append(day2String(this.wkst));
3690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
3700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
3710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        appendNumbers(s, ";BYSECOND=", this.bysecondCount, this.bysecond);
3720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        appendNumbers(s, ";BYMINUTE=", this.byminuteCount, this.byminute);
3730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        appendNumbers(s, ";BYSECOND=", this.byhourCount, this.byhour);
3740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
3750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        // day
3760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        int count = this.bydayCount;
3770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (count > 0) {
3780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            s.append(";BYDAY=");
3790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            count--;
3800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            for (int i=0; i<count; i++) {
3810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                appendByDay(s, i);
3820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                s.append(",");
3830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            }
3840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            appendByDay(s, count);
3850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
3860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
3870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        appendNumbers(s, ";BYMONTHDAY=", this.bymonthdayCount, this.bymonthday);
3880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        appendNumbers(s, ";BYYEARDAY=", this.byyeardayCount, this.byyearday);
3890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        appendNumbers(s, ";BYWEEKNO=", this.byweeknoCount, this.byweekno);
3900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        appendNumbers(s, ";BYMONTH=", this.bymonthCount, this.bymonth);
3910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        appendNumbers(s, ";BYSETPOS=", this.bysetposCount, this.bysetpos);
3920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
3930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        return s.toString();
3940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
3950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
3960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public boolean repeatsOnEveryWeekDay() {
3970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (this.freq != WEEKLY) {
3980d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return false;
3990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
4000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
4010d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        int count = this.bydayCount;
4020d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (count != 5) {
4030d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return false;
4040d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
4050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
4060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        for (int i = 0 ; i < count ; i++) {
4070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            int day = byday[i];
4080d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            if (day == SU || day == SA) {
4090d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return false;
4100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            }
4110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
4120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
4130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        return true;
4140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
4150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
4160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /**
4170d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * Determines whether this rule specifies a simple monthly rule by weekday, such as
4180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * "FREQ=MONTHLY;BYDAY=3TU" (the 3rd Tuesday of every month).
4190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * <p>
4200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * Negative days, e.g. "FREQ=MONTHLY;BYDAY=-1TU" (the last Tuesday of every month),
4210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * will cause "false" to be returned.
4220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * <p>
4230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * Rules that fire every week, such as "FREQ=MONTHLY;BYDAY=TU" (every Tuesday of every
4240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * month) will cause "false" to be returned.  (Note these are usually expressed as
4250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * WEEKLY rules, and hence are uncommon.)
4260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     *
4270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * @return true if this rule is of the appropriate form
4280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     */
4290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public boolean repeatsMonthlyOnDayCount() {
4300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (this.freq != MONTHLY) {
4310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return false;
4320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
4330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
4340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (bydayCount != 1 || bymonthdayCount != 0) {
4350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return false;
4360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
4370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
4380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (bydayNum[0] <= 0) {
4390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return false;
4400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
4410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
4420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        return true;
4430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
4440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
4450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /**
4460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * Determines whether two integer arrays contain identical elements.
4470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * <p>
4480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * The native implementation over-allocated the arrays (and may have stuff left over from
4490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * a previous run), so we can't just check the arrays -- the separately-maintained count
4500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * field also matters.  We assume that a null array will have a count of zero, and that the
4510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * array can hold as many elements as the associated count indicates.
4520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * <p>
4530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * TODO: replace this with Arrays.equals() when the old parser goes away.
4540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     */
4550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static boolean arraysEqual(int[] array1, int count1, int[] array2, int count2) {
4560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (count1 != count2) {
4570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return false;
4580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
4590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
4600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        for (int i = 0; i < count1; i++) {
4610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            if (array1[i] != array2[i])
4620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return false;
4630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
4640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
4650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        return true;
4660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
4670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
4680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    @Override
4690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public boolean equals(Object obj) {
4700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (this == obj) {
4710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return true;
4720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
4730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (!(obj instanceof EventRecurrence)) {
4740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return false;
4750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
4760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
4770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        EventRecurrence er = (EventRecurrence) obj;
4780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        return  (startDate == null ?
4790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                        er.startDate == null : Time.compare(startDate, er.startDate) == 0) &&
4800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                freq == er.freq &&
4810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                (until == null ? er.until == null : until.equals(er.until)) &&
4820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                count == er.count &&
4830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                interval == er.interval &&
4840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                wkst == er.wkst &&
4850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                arraysEqual(bysecond, bysecondCount, er.bysecond, er.bysecondCount) &&
4860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                arraysEqual(byminute, byminuteCount, er.byminute, er.byminuteCount) &&
4870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                arraysEqual(byhour, byhourCount, er.byhour, er.byhourCount) &&
4880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                arraysEqual(byday, bydayCount, er.byday, er.bydayCount) &&
4890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                arraysEqual(bydayNum, bydayCount, er.bydayNum, er.bydayCount) &&
4900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                arraysEqual(bymonthday, bymonthdayCount, er.bymonthday, er.bymonthdayCount) &&
4910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                arraysEqual(byyearday, byyeardayCount, er.byyearday, er.byyeardayCount) &&
4920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                arraysEqual(byweekno, byweeknoCount, er.byweekno, er.byweeknoCount) &&
4930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                arraysEqual(bymonth, bymonthCount, er.bymonth, er.bymonthCount) &&
4940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                arraysEqual(bysetpos, bysetposCount, er.bysetpos, er.bysetposCount);
4950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
4960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
4970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    @Override public int hashCode() {
4980d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        // We overrode equals, so we must override hashCode().  Nobody seems to need this though.
4990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        throw new UnsupportedOperationException();
5000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
5010d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
5020d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /**
5030d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * Resets parser-modified fields to their initial state.  Does not alter startDate.
5040d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * <p>
5050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * The original parser always set all of the "count" fields, "wkst", and "until",
5060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * essentially allowing the same object to be used multiple times by calling parse().
5070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * It's unclear whether this behavior was intentional.  For now, be paranoid and
5080d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * preserve the existing behavior by resetting the fields.
5090d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * <p>
5100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * We don't need to touch the integer arrays; they will either be ignored or
5110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * overwritten.  The "startDate" field is not set by the parser, so we ignore it here.
5120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     */
5130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private void resetFields() {
5140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        until = null;
5150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        freq = count = interval = bysecondCount = byminuteCount = byhourCount =
5160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            bydayCount = bymonthdayCount = byyeardayCount = byweeknoCount = bymonthCount =
5170d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            bysetposCount = 0;
5180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
5190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
5200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /**
5210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * Parses an rfc2445 recurrence rule string into its component pieces.  Attempting to parse
5220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * malformed input will result in an EventRecurrence.InvalidFormatException.
5230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     *
5240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * @param recur The recurrence rule to parse (in un-folded form).
5250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     */
5260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public void parse(String recur) {
5270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        /*
5280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * From RFC 2445 section 4.3.10:
5290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *
5300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * recur = "FREQ"=freq *(
5310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *       ; either UNTIL or COUNT may appear in a 'recur',
5320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *       ; but UNTIL and COUNT MUST NOT occur in the same 'recur'
5330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *
5340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *       ( ";" "UNTIL" "=" enddate ) /
5350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *       ( ";" "COUNT" "=" 1*DIGIT ) /
5360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *
5370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *       ; the rest of these keywords are optional,
5380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *       ; but MUST NOT occur more than once
5390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *
5400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *       ( ";" "INTERVAL" "=" 1*DIGIT )          /
5410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *       ( ";" "BYSECOND" "=" byseclist )        /
5420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *       ( ";" "BYMINUTE" "=" byminlist )        /
5430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *       ( ";" "BYHOUR" "=" byhrlist )           /
5440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *       ( ";" "BYDAY" "=" bywdaylist )          /
5450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *       ( ";" "BYMONTHDAY" "=" bymodaylist )    /
5460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *       ( ";" "BYYEARDAY" "=" byyrdaylist )     /
5470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *       ( ";" "BYWEEKNO" "=" bywknolist )       /
5480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *       ( ";" "BYMONTH" "=" bymolist )          /
5490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *       ( ";" "BYSETPOS" "=" bysplist )         /
5500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *       ( ";" "WKST" "=" weekday )              /
5510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *       ( ";" x-name "=" text )
5520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *       )
5530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *
5546d5684cdf6886a46ee993c8ec986d306472cd5b0Andy McFadden         *  The rule parts are not ordered in any particular sequence.
5556d5684cdf6886a46ee993c8ec986d306472cd5b0Andy McFadden         *
5560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * Examples:
5570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *   FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=1SU,-1SU
5580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *   FREQ=YEARLY;INTERVAL=4;BYMONTH=11;BYDAY=TU;BYMONTHDAY=2,3,4,5,6,7,8
5590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *
5600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * Strategy:
5610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * (1) Split the string at ';' boundaries to get an array of rule "parts".
5620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * (2) For each part, find substrings for left/right sides of '=' (name/value).
5630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * (3) Call a <name>-specific parsing function to parse the <value> into an
5640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *     output field.
5650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *
5660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * By keeping track of which names we've seen in a bit vector, we can verify the
5670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * constraints indicated above (FREQ appears first, none of them appear more than once --
5680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * though x-[name] would require special treatment), and we have either UNTIL or COUNT
5690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * but not both.
5700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *
5710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * In general, RFC 2445 property names (e.g. "FREQ") and enumerations ("TU") must
5720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * be handled in a case-insensitive fashion, but case may be significant for other
5730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * properties.  We don't have any case-sensitive values in RRULE, except possibly
5740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * for the custom "X-" properties, but we ignore those anyway.  Thus, we can trivially
5750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * convert the entire string to upper case and then use simple comparisons.
5760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *
5770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * Differences from previous version:
5780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * - allows lower-case property and enumeration values [optional]
5790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * - enforces that FREQ appears first
5800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * - enforces that only one of UNTIL and COUNT may be specified
5810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * - allows (but ignores) X-* parts
5820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * - improved validation on various values (e.g. UNTIL timestamps)
5830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * - error messages are more specific
5849a91eb9d6c3c28b54223dae453c9d456b0c87355Andy McFadden         *
5859a91eb9d6c3c28b54223dae453c9d456b0c87355Andy McFadden         * TODO: enforce additional constraints listed in RFC 5545, notably the "N/A" entries
5869a91eb9d6c3c28b54223dae453c9d456b0c87355Andy McFadden         * in section 3.3.10.  For example, if FREQ=WEEKLY, we should reject a rule that
5879a91eb9d6c3c28b54223dae453c9d456b0c87355Andy McFadden         * includes a BYMONTHDAY part.
5880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         */
5890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
5900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        /* TODO: replace with "if (freq != 0) throw" if nothing requires this */
5910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        resetFields();
5920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
5930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        int parseFlags = 0;
5940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        String[] parts;
5950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (ALLOW_LOWER_CASE) {
5960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            parts = recur.toUpperCase().split(";");
5970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        } else {
5980d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            parts = recur.split(";");
5990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
6000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        for (String part : parts) {
601a991358d94947cc0dbd6a8d75dd77b5fd377734bAlice Yang            // allow empty part (e.g., double semicolon ";;")
602a991358d94947cc0dbd6a8d75dd77b5fd377734bAlice Yang            if (TextUtils.isEmpty(part)) {
603a991358d94947cc0dbd6a8d75dd77b5fd377734bAlice Yang                continue;
604a991358d94947cc0dbd6a8d75dd77b5fd377734bAlice Yang            }
6050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            int equalIndex = part.indexOf('=');
6060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            if (equalIndex <= 0) {
6070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                /* no '=' or no LHS */
6080d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                throw new InvalidFormatException("Missing LHS in " + part);
6090d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            }
6100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
6110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            String lhs = part.substring(0, equalIndex);
6120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            String rhs = part.substring(equalIndex + 1);
6130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            if (rhs.length() == 0) {
6140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                throw new InvalidFormatException("Missing RHS in " + part);
6150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            }
6160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
6170d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            /*
6180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden             * In lieu of a "switch" statement that allows string arguments, we use a
6190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden             * map from strings to parsing functions.
6200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden             */
6210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            PartParser parser = sParsePartMap.get(lhs);
6220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            if (parser == null) {
6230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                if (lhs.startsWith("X-")) {
6240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                    //Log.d(TAG, "Ignoring custom part " + lhs);
6250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                    continue;
6260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                }
6270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                throw new InvalidFormatException("Couldn't find parser for " + lhs);
6280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            } else {
6290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                int flag = parser.parsePart(rhs, this);
6300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                if ((parseFlags & flag) != 0) {
6310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                    throw new InvalidFormatException("Part " + lhs + " was specified twice");
6320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                }
6330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                parseFlags |= flag;
6340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            }
6350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
6360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
6370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        // If not specified, week starts on Monday.
6380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if ((parseFlags & PARSED_WKST) == 0) {
6390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            wkst = MO;
6400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
6410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
6420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        // FREQ is mandatory.
6430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if ((parseFlags & PARSED_FREQ) == 0) {
6440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            throw new InvalidFormatException("Must specify a FREQ value");
6450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
6460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
6470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        // Can't have both UNTIL and COUNT.
6480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if ((parseFlags & (PARSED_UNTIL | PARSED_COUNT)) == (PARSED_UNTIL | PARSED_COUNT)) {
6490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            if (ONLY_ONE_UNTIL_COUNT) {
6500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                throw new InvalidFormatException("Must not specify both UNTIL and COUNT: " + recur);
6510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            } else {
6520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                Log.w(TAG, "Warning: rrule has both UNTIL and COUNT: " + recur);
6530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            }
6540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
6550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
6560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
6570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /**
6580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * Base class for the RRULE part parsers.
6590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     */
6600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    abstract static class PartParser {
6610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        /**
6620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * Parses a single part.
6630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *
6640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * @param value The right-hand-side of the part.
6650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * @param er The EventRecurrence into which the result is stored.
6660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * @return A bit value indicating which part was parsed.
6670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         */
6680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        public abstract int parsePart(String value, EventRecurrence er);
6690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
6700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        /**
6710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * Parses an integer, with range-checking.
6720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *
6730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * @param str The string to parse.
6740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * @param minVal Minimum allowed value.
6750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * @param maxVal Maximum allowed value.
6760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * @param allowZero Is 0 allowed?
6770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * @return The parsed value.
6780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         */
6790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        public static int parseIntRange(String str, int minVal, int maxVal, boolean allowZero) {
6800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            try {
6810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                if (str.charAt(0) == '+') {
6820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                    // Integer.parseInt does not allow a leading '+', so skip it manually.
6830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                    str = str.substring(1);
6840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                }
6850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                int val = Integer.parseInt(str);
6860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                if (val < minVal || val > maxVal || (val == 0 && !allowZero)) {
6870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                    throw new InvalidFormatException("Integer value out of range: " + str);
6880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                }
6890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return val;
6900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            } catch (NumberFormatException nfe) {
6910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                throw new InvalidFormatException("Invalid integer value: " + str);
6920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            }
6930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
6940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
6950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        /**
6960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * Parses a comma-separated list of integers, with range-checking.
6970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         *
6980d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * @param listStr The string to parse.
6990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * @param minVal Minimum allowed value.
7000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * @param maxVal Maximum allowed value.
7010d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * @param allowZero Is 0 allowed?
7020d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         * @return A new array with values, sized to hold the exact number of elements.
7030d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden         */
7040d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        public static int[] parseNumberList(String listStr, int minVal, int maxVal,
7050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                boolean allowZero) {
7060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            int[] values;
7070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
7080d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            if (listStr.indexOf(",") < 0) {
7090d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                // Common case: only one entry, skip split() overhead.
7100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                values = new int[1];
7110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                values[0] = parseIntRange(listStr, minVal, maxVal, allowZero);
7120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            } else {
7130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                String[] valueStrs = listStr.split(",");
7140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                int len = valueStrs.length;
7150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                values = new int[len];
7160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                for (int i = 0; i < len; i++) {
7170d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                    values[i] = parseIntRange(valueStrs[i], minVal, maxVal, allowZero);
7180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                }
7190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            }
7200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return values;
7210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
7220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden   }
7230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
7240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /** parses FREQ={SECONDLY,MINUTELY,...} */
7250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static class ParseFreq extends PartParser {
7260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        @Override public int parsePart(String value, EventRecurrence er) {
7270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            Integer freq = sParseFreqMap.get(value);
7280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            if (freq == null) {
7290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                throw new InvalidFormatException("Invalid FREQ value: " + value);
7300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            }
7310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            er.freq = freq;
7320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return PARSED_FREQ;
7330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
7340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
7350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /** parses UNTIL=enddate, e.g. "19970829T021400" */
7360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static class ParseUntil extends PartParser {
7370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        @Override public int parsePart(String value, EventRecurrence er) {
7380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            if (VALIDATE_UNTIL) {
7390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                try {
7400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                    // Parse the time to validate it.  The result isn't retained.
7410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                    Time until = new Time();
7420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                    until.parse(value);
7430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                } catch (TimeFormatException tfe) {
7440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                    throw new InvalidFormatException("Invalid UNTIL value: " + value);
7450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                }
7460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            }
7470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            er.until = value;
7480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return PARSED_UNTIL;
7490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
7500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
7510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /** parses COUNT=[non-negative-integer] */
7520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static class ParseCount extends PartParser {
7530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        @Override public int parsePart(String value, EventRecurrence er) {
7540335d9f524da1da1f4d15bba37a1576a9480a36dMichael Chan            er.count = parseIntRange(value, Integer.MIN_VALUE, Integer.MAX_VALUE, true);
7550335d9f524da1da1f4d15bba37a1576a9480a36dMichael Chan            if (er.count < 0) {
7560335d9f524da1da1f4d15bba37a1576a9480a36dMichael Chan                Log.d(TAG, "Invalid Count. Forcing COUNT to 1 from " + value);
7570335d9f524da1da1f4d15bba37a1576a9480a36dMichael Chan                er.count = 1; // invalid count. assume one time recurrence.
7580335d9f524da1da1f4d15bba37a1576a9480a36dMichael Chan            }
7590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return PARSED_COUNT;
7600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
7610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
7620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /** parses INTERVAL=[non-negative-integer] */
7630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static class ParseInterval extends PartParser {
7640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        @Override public int parsePart(String value, EventRecurrence er) {
7650335d9f524da1da1f4d15bba37a1576a9480a36dMichael Chan            er.interval = parseIntRange(value, Integer.MIN_VALUE, Integer.MAX_VALUE, true);
7660335d9f524da1da1f4d15bba37a1576a9480a36dMichael Chan            if (er.interval < 1) {
7670335d9f524da1da1f4d15bba37a1576a9480a36dMichael Chan                Log.d(TAG, "Invalid Interval. Forcing INTERVAL to 1 from " + value);
7680335d9f524da1da1f4d15bba37a1576a9480a36dMichael Chan                er.interval = 1;
7690335d9f524da1da1f4d15bba37a1576a9480a36dMichael Chan            }
7700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return PARSED_INTERVAL;
7710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
7720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
7730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /** parses BYSECOND=byseclist */
7740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static class ParseBySecond extends PartParser {
7750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        @Override public int parsePart(String value, EventRecurrence er) {
7760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            int[] bysecond = parseNumberList(value, 0, 59, true);
7770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            er.bysecond = bysecond;
7780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            er.bysecondCount = bysecond.length;
7790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return PARSED_BYSECOND;
7800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
7810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
7820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /** parses BYMINUTE=byminlist */
7830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static class ParseByMinute extends PartParser {
7840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        @Override public int parsePart(String value, EventRecurrence er) {
7850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            int[] byminute = parseNumberList(value, 0, 59, true);
7860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            er.byminute = byminute;
7870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            er.byminuteCount = byminute.length;
7880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return PARSED_BYMINUTE;
7890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
7900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
7910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /** parses BYHOUR=byhrlist */
7920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static class ParseByHour extends PartParser {
7930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        @Override public int parsePart(String value, EventRecurrence er) {
7940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            int[] byhour = parseNumberList(value, 0, 23, true);
7950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            er.byhour = byhour;
7960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            er.byhourCount = byhour.length;
7970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return PARSED_BYHOUR;
7980d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
7990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
8000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /** parses BYDAY=bywdaylist, e.g. "1SU,-1SU" */
8010d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static class ParseByDay extends PartParser {
8020d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        @Override public int parsePart(String value, EventRecurrence er) {
8030d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            int[] byday;
8040d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            int[] bydayNum;
8050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            int bydayCount;
8060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
8070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            if (value.indexOf(",") < 0) {
8080d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                /* only one entry, skip split() overhead */
8090d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                bydayCount = 1;
8100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                byday = new int[1];
8110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                bydayNum = new int[1];
8120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                parseWday(value, byday, bydayNum, 0);
8130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            } else {
8140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                String[] wdays = value.split(",");
8150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                int len = wdays.length;
8160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                bydayCount = len;
8170d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                byday = new int[len];
8180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                bydayNum = new int[len];
8190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                for (int i = 0; i < len; i++) {
8200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                    parseWday(wdays[i], byday, bydayNum, i);
8210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                }
8220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            }
8230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            er.byday = byday;
8240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            er.bydayNum = bydayNum;
8250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            er.bydayCount = bydayCount;
8260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return PARSED_BYDAY;
8270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
8280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
8290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        /** parses [int]weekday, putting the pieces into parallel array entries */
8300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        private static void parseWday(String str, int[] byday, int[] bydayNum, int index) {
8310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            int wdayStrStart = str.length() - 2;
8320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            String wdayStr;
8330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
8340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            if (wdayStrStart > 0) {
8350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                /* number is included; parse it out and advance to weekday */
8360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                String numPart = str.substring(0, wdayStrStart);
8370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                int num = parseIntRange(numPart, -53, 53, false);
8380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                bydayNum[index] = num;
8390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                wdayStr = str.substring(wdayStrStart);
8400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            } else {
8410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                /* just the weekday string */
8420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                wdayStr = str;
8430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            }
8440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            Integer wday = sParseWeekdayMap.get(wdayStr);
8450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            if (wday == null) {
8460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                throw new InvalidFormatException("Invalid BYDAY value: " + str);
8470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            }
8480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            byday[index] = wday;
8490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
8500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
8510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /** parses BYMONTHDAY=bymodaylist */
8520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static class ParseByMonthDay extends PartParser {
8530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        @Override public int parsePart(String value, EventRecurrence er) {
8540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            int[] bymonthday = parseNumberList(value, -31, 31, false);
8550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            er.bymonthday = bymonthday;
8560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            er.bymonthdayCount = bymonthday.length;
8570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return PARSED_BYMONTHDAY;
8580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
8590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
8600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /** parses BYYEARDAY=byyrdaylist */
8610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static class ParseByYearDay extends PartParser {
8620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        @Override public int parsePart(String value, EventRecurrence er) {
8630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            int[] byyearday = parseNumberList(value, -366, 366, false);
8640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            er.byyearday = byyearday;
8650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            er.byyeardayCount = byyearday.length;
8660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return PARSED_BYYEARDAY;
8670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
8680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
8690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /** parses BYWEEKNO=bywknolist */
8700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static class ParseByWeekNo extends PartParser {
8710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        @Override public int parsePart(String value, EventRecurrence er) {
8720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            int[] byweekno = parseNumberList(value, -53, 53, false);
8730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            er.byweekno = byweekno;
8740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            er.byweeknoCount = byweekno.length;
8750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return PARSED_BYWEEKNO;
8760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
8770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
8780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /** parses BYMONTH=bymolist */
8790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static class ParseByMonth extends PartParser {
8800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        @Override public int parsePart(String value, EventRecurrence er) {
8810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            int[] bymonth = parseNumberList(value, 1, 12, false);
8820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            er.bymonth = bymonth;
8830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            er.bymonthCount = bymonth.length;
8840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return PARSED_BYMONTH;
8850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
8860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
8870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /** parses BYSETPOS=bysplist */
8880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static class ParseBySetPos extends PartParser {
8890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        @Override public int parsePart(String value, EventRecurrence er) {
8900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            int[] bysetpos = parseNumberList(value, Integer.MIN_VALUE, Integer.MAX_VALUE, true);
8910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            er.bysetpos = bysetpos;
8920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            er.bysetposCount = bysetpos.length;
8930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return PARSED_BYSETPOS;
8940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
8950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
8960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /** parses WKST={SU,MO,...} */
8970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static class ParseWkst extends PartParser {
8980d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        @Override public int parsePart(String value, EventRecurrence er) {
8990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            Integer wkst = sParseWeekdayMap.get(value);
9000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            if (wkst == null) {
9010d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                throw new InvalidFormatException("Invalid WKST value: " + value);
9020d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            }
9030d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            er.wkst = wkst;
9040d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return PARSED_WKST;
9050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
9060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
9070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden}
908