EventRecurrencepublic class EventRecurrence extends Object Event recurrence utility functions. |
Fields Summary |
---|
private static String | TAG | public static final int | SECONDLY | public static final int | MINUTELY | public static final int | HOURLY | public static final int | DAILY | public static final int | WEEKLY | public static final int | MONTHLY | public static final int | YEARLY | public static final int | SU | public static final int | MO | public static final int | TU | public static final int | WE | public static final int | TH | public static final int | FR | public static final int | SA | public android.text.format.Time | startDate | public int | freq | public String | until | public int | count | public int | interval | public int | wkst | public int[] | bysecond | public int | bysecondCount | public int[] | byminute | public int | byminuteCount | public int[] | byhour | public int | byhourCount | public int[] | byday | public int[] | bydayNum | public int | bydayCount | public int[] | bymonthday | public int | bymonthdayCount | public int[] | byyearday | public int | byyeardayCount | public int[] | byweekno | public int | byweeknoCount | public int[] | bymonth | public int | bymonthCount | public int[] | bysetpos | public int | bysetposCount | private static HashMap | sParsePartMapmaps a part string to a parser object | private static final int | PARSED_FREQ | private static final int | PARSED_UNTIL | private static final int | PARSED_COUNT | private static final int | PARSED_INTERVAL | private static final int | PARSED_BYSECOND | private static final int | PARSED_BYMINUTE | private static final int | PARSED_BYHOUR | private static final int | PARSED_BYDAY | private static final int | PARSED_BYMONTHDAY | private static final int | PARSED_BYYEARDAY | private static final int | PARSED_BYWEEKNO | private static final int | PARSED_BYMONTH | private static final int | PARSED_BYSETPOS | private static final int | PARSED_WKST | private static final HashMap | sParseFreqMapmaps a FREQ value to an integer constant | private static final HashMap | sParseWeekdayMapmaps a two-character weekday string to an integer constant | private static final boolean | ALLOW_LOWER_CASEIf set, allow lower-case recurrence rule strings. Minor performance impact. | private static final boolean | VALIDATE_UNTILIf set, validate the value of UNTIL parts. Minor performance impact. | private static final boolean | ONLY_ONE_UNTIL_COUNTIf set, require that only one of {UNTIL,COUNT} is present. Breaks compat w/ old parser. |
Methods Summary |
---|
private void | appendByDay(java.lang.StringBuilder s, int i)
int n = this.bydayNum[i];
if (n != 0) {
s.append(n);
}
String str = day2String(this.byday[i]);
s.append(str);
| private static void | appendNumbers(java.lang.StringBuilder s, java.lang.String label, int count, int[] values)
if (count > 0) {
s.append(label);
count--;
for (int i=0; i<count; i++) {
s.append(values[i]);
s.append(",");
}
s.append(values[count]);
}
| private static boolean | arraysEqual(int[] array1, int count1, int[] array2, int count2)Determines whether two integer arrays contain identical elements.
The native implementation over-allocated the arrays (and may have stuff left over from
a previous run), so we can't just check the arrays -- the separately-maintained count
field also matters. We assume that a null array will have a count of zero, and that the
array can hold as many elements as the associated count indicates.
TODO: replace this with Arrays.equals() when the old parser goes away.
if (count1 != count2) {
return false;
}
for (int i = 0; i < count1; i++) {
if (array1[i] != array2[i])
return false;
}
return true;
| public static int | calendarDay2Day(int day)Converts one of the Calendar.SUNDAY constants to the SU, MO, etc.
constants. btw, I think we should switch to those here too, to
get rid of this function, if possible.
switch (day)
{
case Calendar.SUNDAY:
return SU;
case Calendar.MONDAY:
return MO;
case Calendar.TUESDAY:
return TU;
case Calendar.WEDNESDAY:
return WE;
case Calendar.THURSDAY:
return TH;
case Calendar.FRIDAY:
return FR;
case Calendar.SATURDAY:
return SA;
default:
throw new RuntimeException("bad day of week: " + day);
}
| public static int | day2CalendarDay(int day)Converts one of the SU, MO, etc. constants to the Calendar.SUNDAY
constants. btw, I think we should switch to those here too, to
get rid of this function, if possible.
switch (day)
{
case SU:
return Calendar.SUNDAY;
case MO:
return Calendar.MONDAY;
case TU:
return Calendar.TUESDAY;
case WE:
return Calendar.WEDNESDAY;
case TH:
return Calendar.THURSDAY;
case FR:
return Calendar.FRIDAY;
case SA:
return Calendar.SATURDAY;
default:
throw new RuntimeException("bad day of week: " + day);
}
| private static java.lang.String | day2String(int day)Converts one of the internal day constants (SU, MO, etc.) to the
two-letter string representing that constant.
switch (day) {
case SU:
return "SU";
case MO:
return "MO";
case TU:
return "TU";
case WE:
return "WE";
case TH:
return "TH";
case FR:
return "FR";
case SA:
return "SA";
default:
throw new IllegalArgumentException("bad day argument: " + day);
}
| public static int | day2TimeDay(int day)
switch (day)
{
case SU:
return Time.SUNDAY;
case MO:
return Time.MONDAY;
case TU:
return Time.TUESDAY;
case WE:
return Time.WEDNESDAY;
case TH:
return Time.THURSDAY;
case FR:
return Time.FRIDAY;
case SA:
return Time.SATURDAY;
default:
throw new RuntimeException("bad day of week: " + day);
}
| public boolean | equals(java.lang.Object obj)
if (this == obj) {
return true;
}
if (!(obj instanceof EventRecurrence)) {
return false;
}
EventRecurrence er = (EventRecurrence) obj;
return (startDate == null ?
er.startDate == null : Time.compare(startDate, er.startDate) == 0) &&
freq == er.freq &&
(until == null ? er.until == null : until.equals(er.until)) &&
count == er.count &&
interval == er.interval &&
wkst == er.wkst &&
arraysEqual(bysecond, bysecondCount, er.bysecond, er.bysecondCount) &&
arraysEqual(byminute, byminuteCount, er.byminute, er.byminuteCount) &&
arraysEqual(byhour, byhourCount, er.byhour, er.byhourCount) &&
arraysEqual(byday, bydayCount, er.byday, er.bydayCount) &&
arraysEqual(bydayNum, bydayCount, er.bydayNum, er.bydayCount) &&
arraysEqual(bymonthday, bymonthdayCount, er.bymonthday, er.bymonthdayCount) &&
arraysEqual(byyearday, byyeardayCount, er.byyearday, er.byyeardayCount) &&
arraysEqual(byweekno, byweeknoCount, er.byweekno, er.byweeknoCount) &&
arraysEqual(bymonth, bymonthCount, er.bymonth, er.bymonthCount) &&
arraysEqual(bysetpos, bysetposCount, er.bysetpos, er.bysetposCount);
| public int | hashCode()
// We overrode equals, so we must override hashCode(). Nobody seems to need this though.
throw new UnsupportedOperationException();
| public void | parse(java.lang.String recur)Parses an rfc2445 recurrence rule string into its component pieces. Attempting to parse
malformed input will result in an EventRecurrence.InvalidFormatException.
/*
* From RFC 2445 section 4.3.10:
*
* recur = "FREQ"=freq *(
* ; either UNTIL or COUNT may appear in a 'recur',
* ; but UNTIL and COUNT MUST NOT occur in the same 'recur'
*
* ( ";" "UNTIL" "=" enddate ) /
* ( ";" "COUNT" "=" 1*DIGIT ) /
*
* ; the rest of these keywords are optional,
* ; but MUST NOT occur more than once
*
* ( ";" "INTERVAL" "=" 1*DIGIT ) /
* ( ";" "BYSECOND" "=" byseclist ) /
* ( ";" "BYMINUTE" "=" byminlist ) /
* ( ";" "BYHOUR" "=" byhrlist ) /
* ( ";" "BYDAY" "=" bywdaylist ) /
* ( ";" "BYMONTHDAY" "=" bymodaylist ) /
* ( ";" "BYYEARDAY" "=" byyrdaylist ) /
* ( ";" "BYWEEKNO" "=" bywknolist ) /
* ( ";" "BYMONTH" "=" bymolist ) /
* ( ";" "BYSETPOS" "=" bysplist ) /
* ( ";" "WKST" "=" weekday ) /
* ( ";" x-name "=" text )
* )
*
* The rule parts are not ordered in any particular sequence.
*
* Examples:
* FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=1SU,-1SU
* FREQ=YEARLY;INTERVAL=4;BYMONTH=11;BYDAY=TU;BYMONTHDAY=2,3,4,5,6,7,8
*
* Strategy:
* (1) Split the string at ';' boundaries to get an array of rule "parts".
* (2) For each part, find substrings for left/right sides of '=' (name/value).
* (3) Call a <name>-specific parsing function to parse the <value> into an
* output field.
*
* By keeping track of which names we've seen in a bit vector, we can verify the
* constraints indicated above (FREQ appears first, none of them appear more than once --
* though x-[name] would require special treatment), and we have either UNTIL or COUNT
* but not both.
*
* In general, RFC 2445 property names (e.g. "FREQ") and enumerations ("TU") must
* be handled in a case-insensitive fashion, but case may be significant for other
* properties. We don't have any case-sensitive values in RRULE, except possibly
* for the custom "X-" properties, but we ignore those anyway. Thus, we can trivially
* convert the entire string to upper case and then use simple comparisons.
*
* Differences from previous version:
* - allows lower-case property and enumeration values [optional]
* - enforces that FREQ appears first
* - enforces that only one of UNTIL and COUNT may be specified
* - allows (but ignores) X-* parts
* - improved validation on various values (e.g. UNTIL timestamps)
* - error messages are more specific
*
* TODO: enforce additional constraints listed in RFC 5545, notably the "N/A" entries
* in section 3.3.10. For example, if FREQ=WEEKLY, we should reject a rule that
* includes a BYMONTHDAY part.
*/
/* TODO: replace with "if (freq != 0) throw" if nothing requires this */
resetFields();
int parseFlags = 0;
String[] parts;
if (ALLOW_LOWER_CASE) {
parts = recur.toUpperCase().split(";");
} else {
parts = recur.split(";");
}
for (String part : parts) {
// allow empty part (e.g., double semicolon ";;")
if (TextUtils.isEmpty(part)) {
continue;
}
int equalIndex = part.indexOf('=");
if (equalIndex <= 0) {
/* no '=' or no LHS */
throw new InvalidFormatException("Missing LHS in " + part);
}
String lhs = part.substring(0, equalIndex);
String rhs = part.substring(equalIndex + 1);
if (rhs.length() == 0) {
throw new InvalidFormatException("Missing RHS in " + part);
}
/*
* In lieu of a "switch" statement that allows string arguments, we use a
* map from strings to parsing functions.
*/
PartParser parser = sParsePartMap.get(lhs);
if (parser == null) {
if (lhs.startsWith("X-")) {
//Log.d(TAG, "Ignoring custom part " + lhs);
continue;
}
throw new InvalidFormatException("Couldn't find parser for " + lhs);
} else {
int flag = parser.parsePart(rhs, this);
if ((parseFlags & flag) != 0) {
throw new InvalidFormatException("Part " + lhs + " was specified twice");
}
parseFlags |= flag;
}
}
// If not specified, week starts on Monday.
if ((parseFlags & PARSED_WKST) == 0) {
wkst = MO;
}
// FREQ is mandatory.
if ((parseFlags & PARSED_FREQ) == 0) {
throw new InvalidFormatException("Must specify a FREQ value");
}
// Can't have both UNTIL and COUNT.
if ((parseFlags & (PARSED_UNTIL | PARSED_COUNT)) == (PARSED_UNTIL | PARSED_COUNT)) {
if (ONLY_ONE_UNTIL_COUNT) {
throw new InvalidFormatException("Must not specify both UNTIL and COUNT: " + recur);
} else {
Log.w(TAG, "Warning: rrule has both UNTIL and COUNT: " + recur);
}
}
| public boolean | repeatsMonthlyOnDayCount()Determines whether this rule specifies a simple monthly rule by weekday, such as
"FREQ=MONTHLY;BYDAY=3TU" (the 3rd Tuesday of every month).
Negative days, e.g. "FREQ=MONTHLY;BYDAY=-1TU" (the last Tuesday of every month),
will cause "false" to be returned.
Rules that fire every week, such as "FREQ=MONTHLY;BYDAY=TU" (every Tuesday of every
month) will cause "false" to be returned. (Note these are usually expressed as
WEEKLY rules, and hence are uncommon.)
if (this.freq != MONTHLY) {
return false;
}
if (bydayCount != 1 || bymonthdayCount != 0) {
return false;
}
if (bydayNum[0] <= 0) {
return false;
}
return true;
| public boolean | repeatsOnEveryWeekDay()
if (this.freq != WEEKLY) {
return false;
}
int count = this.bydayCount;
if (count != 5) {
return false;
}
for (int i = 0 ; i < count ; i++) {
int day = byday[i];
if (day == SU || day == SA) {
return false;
}
}
return true;
| private void | resetFields()Resets parser-modified fields to their initial state. Does not alter startDate.
The original parser always set all of the "count" fields, "wkst", and "until",
essentially allowing the same object to be used multiple times by calling parse().
It's unclear whether this behavior was intentional. For now, be paranoid and
preserve the existing behavior by resetting the fields.
We don't need to touch the integer arrays; they will either be ignored or
overwritten. The "startDate" field is not set by the parser, so we ignore it here.
until = null;
freq = count = interval = bysecondCount = byminuteCount = byhourCount =
bydayCount = bymonthdayCount = byyeardayCount = byweeknoCount = bymonthCount =
bysetposCount = 0;
| public void | setStartDate(android.text.format.Time date)
startDate = date;
| public static int | timeDay2Day(int day)
switch (day)
{
case Time.SUNDAY:
return SU;
case Time.MONDAY:
return MO;
case Time.TUESDAY:
return TU;
case Time.WEDNESDAY:
return WE;
case Time.THURSDAY:
return TH;
case Time.FRIDAY:
return FR;
case Time.SATURDAY:
return SA;
default:
throw new RuntimeException("bad day of week: " + day);
}
| public java.lang.String | toString()
StringBuilder s = new StringBuilder();
s.append("FREQ=");
switch (this.freq)
{
case SECONDLY:
s.append("SECONDLY");
break;
case MINUTELY:
s.append("MINUTELY");
break;
case HOURLY:
s.append("HOURLY");
break;
case DAILY:
s.append("DAILY");
break;
case WEEKLY:
s.append("WEEKLY");
break;
case MONTHLY:
s.append("MONTHLY");
break;
case YEARLY:
s.append("YEARLY");
break;
}
if (!TextUtils.isEmpty(this.until)) {
s.append(";UNTIL=");
s.append(until);
}
if (this.count != 0) {
s.append(";COUNT=");
s.append(this.count);
}
if (this.interval != 0) {
s.append(";INTERVAL=");
s.append(this.interval);
}
if (this.wkst != 0) {
s.append(";WKST=");
s.append(day2String(this.wkst));
}
appendNumbers(s, ";BYSECOND=", this.bysecondCount, this.bysecond);
appendNumbers(s, ";BYMINUTE=", this.byminuteCount, this.byminute);
appendNumbers(s, ";BYSECOND=", this.byhourCount, this.byhour);
// day
int count = this.bydayCount;
if (count > 0) {
s.append(";BYDAY=");
count--;
for (int i=0; i<count; i++) {
appendByDay(s, i);
s.append(",");
}
appendByDay(s, count);
}
appendNumbers(s, ";BYMONTHDAY=", this.bymonthdayCount, this.bymonthday);
appendNumbers(s, ";BYYEARDAY=", this.byyeardayCount, this.byyearday);
appendNumbers(s, ";BYWEEKNO=", this.byweeknoCount, this.byweekno);
appendNumbers(s, ";BYMONTH=", this.bymonthCount, this.bymonth);
appendNumbers(s, ";BYSETPOS=", this.bysetposCount, this.bysetpos);
return s.toString();
|
|