Relocate common Calendar classes
Move some classes from android.pim to com.android.calendarcommon. Bug 4575374 Change-Id: Iab09cb331a0a74f6b951ab5e5b7c207e8f1b939c
This commit is contained in:
@@ -1,892 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2006 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.pim;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.text.format.Time;
|
||||
import android.util.Log;
|
||||
import android.util.TimeFormatException;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Event recurrence utility functions.
|
||||
*/
|
||||
public class EventRecurrence {
|
||||
private static String TAG = "EventRecur";
|
||||
|
||||
public static final int SECONDLY = 1;
|
||||
public static final int MINUTELY = 2;
|
||||
public static final int HOURLY = 3;
|
||||
public static final int DAILY = 4;
|
||||
public static final int WEEKLY = 5;
|
||||
public static final int MONTHLY = 6;
|
||||
public static final int YEARLY = 7;
|
||||
|
||||
public static final int SU = 0x00010000;
|
||||
public static final int MO = 0x00020000;
|
||||
public static final int TU = 0x00040000;
|
||||
public static final int WE = 0x00080000;
|
||||
public static final int TH = 0x00100000;
|
||||
public static final int FR = 0x00200000;
|
||||
public static final int SA = 0x00400000;
|
||||
|
||||
public Time startDate; // set by setStartDate(), not parse()
|
||||
|
||||
public int freq; // SECONDLY, MINUTELY, etc.
|
||||
public String until;
|
||||
public int count;
|
||||
public int interval;
|
||||
public int wkst; // SU, MO, TU, etc.
|
||||
|
||||
/* lists with zero entries may be null references */
|
||||
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;
|
||||
|
||||
/** maps a part string to a parser object */
|
||||
private static HashMap<String,PartParser> sParsePartMap;
|
||||
static {
|
||||
sParsePartMap = new HashMap<String,PartParser>();
|
||||
sParsePartMap.put("FREQ", new ParseFreq());
|
||||
sParsePartMap.put("UNTIL", new ParseUntil());
|
||||
sParsePartMap.put("COUNT", new ParseCount());
|
||||
sParsePartMap.put("INTERVAL", new ParseInterval());
|
||||
sParsePartMap.put("BYSECOND", new ParseBySecond());
|
||||
sParsePartMap.put("BYMINUTE", new ParseByMinute());
|
||||
sParsePartMap.put("BYHOUR", new ParseByHour());
|
||||
sParsePartMap.put("BYDAY", new ParseByDay());
|
||||
sParsePartMap.put("BYMONTHDAY", new ParseByMonthDay());
|
||||
sParsePartMap.put("BYYEARDAY", new ParseByYearDay());
|
||||
sParsePartMap.put("BYWEEKNO", new ParseByWeekNo());
|
||||
sParsePartMap.put("BYMONTH", new ParseByMonth());
|
||||
sParsePartMap.put("BYSETPOS", new ParseBySetPos());
|
||||
sParsePartMap.put("WKST", new ParseWkst());
|
||||
}
|
||||
|
||||
/* values for bit vector that keeps track of what we have already seen */
|
||||
private static final int PARSED_FREQ = 1 << 0;
|
||||
private static final int PARSED_UNTIL = 1 << 1;
|
||||
private static final int PARSED_COUNT = 1 << 2;
|
||||
private static final int PARSED_INTERVAL = 1 << 3;
|
||||
private static final int PARSED_BYSECOND = 1 << 4;
|
||||
private static final int PARSED_BYMINUTE = 1 << 5;
|
||||
private static final int PARSED_BYHOUR = 1 << 6;
|
||||
private static final int PARSED_BYDAY = 1 << 7;
|
||||
private static final int PARSED_BYMONTHDAY = 1 << 8;
|
||||
private static final int PARSED_BYYEARDAY = 1 << 9;
|
||||
private static final int PARSED_BYWEEKNO = 1 << 10;
|
||||
private static final int PARSED_BYMONTH = 1 << 11;
|
||||
private static final int PARSED_BYSETPOS = 1 << 12;
|
||||
private static final int PARSED_WKST = 1 << 13;
|
||||
|
||||
/** maps a FREQ value to an integer constant */
|
||||
private static final HashMap<String,Integer> sParseFreqMap = new HashMap<String,Integer>();
|
||||
static {
|
||||
sParseFreqMap.put("SECONDLY", SECONDLY);
|
||||
sParseFreqMap.put("MINUTELY", MINUTELY);
|
||||
sParseFreqMap.put("HOURLY", HOURLY);
|
||||
sParseFreqMap.put("DAILY", DAILY);
|
||||
sParseFreqMap.put("WEEKLY", WEEKLY);
|
||||
sParseFreqMap.put("MONTHLY", MONTHLY);
|
||||
sParseFreqMap.put("YEARLY", YEARLY);
|
||||
}
|
||||
|
||||
/** maps a two-character weekday string to an integer constant */
|
||||
private static final HashMap<String,Integer> sParseWeekdayMap = new HashMap<String,Integer>();
|
||||
static {
|
||||
sParseWeekdayMap.put("SU", SU);
|
||||
sParseWeekdayMap.put("MO", MO);
|
||||
sParseWeekdayMap.put("TU", TU);
|
||||
sParseWeekdayMap.put("WE", WE);
|
||||
sParseWeekdayMap.put("TH", TH);
|
||||
sParseWeekdayMap.put("FR", FR);
|
||||
sParseWeekdayMap.put("SA", SA);
|
||||
}
|
||||
|
||||
/** If set, allow lower-case recurrence rule strings. Minor performance impact. */
|
||||
private static final boolean ALLOW_LOWER_CASE = false;
|
||||
|
||||
/** If set, validate the value of UNTIL parts. Minor performance impact. */
|
||||
private static final boolean VALIDATE_UNTIL = false;
|
||||
|
||||
/** If set, require that only one of {UNTIL,COUNT} is present. Breaks compat w/ old parser. */
|
||||
private static final boolean ONLY_ONE_UNTIL_COUNT = false;
|
||||
|
||||
|
||||
/**
|
||||
* Thrown when a recurrence string provided can not be parsed according
|
||||
* to RFC2445.
|
||||
*/
|
||||
public static class InvalidFormatException extends RuntimeException {
|
||||
InvalidFormatException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void setStartDate(Time date) {
|
||||
startDate = date;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public static int calendarDay2Day(int day)
|
||||
{
|
||||
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 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 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public static int day2CalendarDay(int day)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts one of the internal day constants (SU, MO, etc.) to the
|
||||
* two-letter string representing that constant.
|
||||
*
|
||||
* @param day one the internal constants SU, MO, etc.
|
||||
* @return the two-letter string for the day ("SU", "MO", etc.)
|
||||
*
|
||||
* @throws IllegalArgumentException Thrown if the day argument is not one of
|
||||
* the defined day constants.
|
||||
*/
|
||||
private static String day2String(int day) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private static void appendNumbers(StringBuilder s, 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 void appendByDay(StringBuilder s, int i)
|
||||
{
|
||||
int n = this.bydayNum[i];
|
||||
if (n != 0) {
|
||||
s.append(n);
|
||||
}
|
||||
|
||||
String str = day2String(this.byday[i]);
|
||||
s.append(str);
|
||||
}
|
||||
|
||||
@Override
|
||||
public 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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether this rule specifies a simple monthly rule by weekday, such as
|
||||
* "FREQ=MONTHLY;BYDAY=3TU" (the 3rd Tuesday of every month).
|
||||
* <p>
|
||||
* Negative days, e.g. "FREQ=MONTHLY;BYDAY=-1TU" (the last Tuesday of every month),
|
||||
* will cause "false" to be returned.
|
||||
* <p>
|
||||
* 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.)
|
||||
*
|
||||
* @return true if this rule is of the appropriate form
|
||||
*/
|
||||
public boolean repeatsMonthlyOnDayCount() {
|
||||
if (this.freq != MONTHLY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bydayCount != 1 || bymonthdayCount != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bydayNum[0] <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether two integer arrays contain identical elements.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* TODO: replace this with Arrays.equals() when the old parser goes away.
|
||||
*/
|
||||
private static boolean arraysEqual(int[] array1, int count1, int[] array2, int count2) {
|
||||
if (count1 != count2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count1; i++) {
|
||||
if (array1[i] != array2[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(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);
|
||||
}
|
||||
|
||||
@Override public int hashCode() {
|
||||
// We overrode equals, so we must override hashCode(). Nobody seems to need this though.
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets parser-modified fields to their initial state. Does not alter startDate.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
*/
|
||||
private void resetFields() {
|
||||
until = null;
|
||||
freq = count = interval = bysecondCount = byminuteCount = byhourCount =
|
||||
bydayCount = bymonthdayCount = byyeardayCount = byweeknoCount = bymonthCount =
|
||||
bysetposCount = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an rfc2445 recurrence rule string into its component pieces. Attempting to parse
|
||||
* malformed input will result in an EventRecurrence.InvalidFormatException.
|
||||
*
|
||||
* @param recur The recurrence rule to parse (in un-folded form).
|
||||
*/
|
||||
public void parse(String recur) {
|
||||
/*
|
||||
* 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 )
|
||||
* )
|
||||
*
|
||||
* 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: 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) {
|
||||
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");
|
||||
}
|
||||
if (parseFlags == 0 && flag != PARSED_FREQ) {
|
||||
throw new InvalidFormatException("FREQ must be specified first");
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for the RRULE part parsers.
|
||||
*/
|
||||
abstract static class PartParser {
|
||||
/**
|
||||
* Parses a single part.
|
||||
*
|
||||
* @param value The right-hand-side of the part.
|
||||
* @param er The EventRecurrence into which the result is stored.
|
||||
* @return A bit value indicating which part was parsed.
|
||||
*/
|
||||
public abstract int parsePart(String value, EventRecurrence er);
|
||||
|
||||
/**
|
||||
* Parses an integer, with range-checking.
|
||||
*
|
||||
* @param str The string to parse.
|
||||
* @param minVal Minimum allowed value.
|
||||
* @param maxVal Maximum allowed value.
|
||||
* @param allowZero Is 0 allowed?
|
||||
* @return The parsed value.
|
||||
*/
|
||||
public static int parseIntRange(String str, int minVal, int maxVal, boolean allowZero) {
|
||||
try {
|
||||
if (str.charAt(0) == '+') {
|
||||
// Integer.parseInt does not allow a leading '+', so skip it manually.
|
||||
str = str.substring(1);
|
||||
}
|
||||
int val = Integer.parseInt(str);
|
||||
if (val < minVal || val > maxVal || (val == 0 && !allowZero)) {
|
||||
throw new InvalidFormatException("Integer value out of range: " + str);
|
||||
}
|
||||
return val;
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new InvalidFormatException("Invalid integer value: " + str);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a comma-separated list of integers, with range-checking.
|
||||
*
|
||||
* @param listStr The string to parse.
|
||||
* @param minVal Minimum allowed value.
|
||||
* @param maxVal Maximum allowed value.
|
||||
* @param allowZero Is 0 allowed?
|
||||
* @return A new array with values, sized to hold the exact number of elements.
|
||||
*/
|
||||
public static int[] parseNumberList(String listStr, int minVal, int maxVal,
|
||||
boolean allowZero) {
|
||||
int[] values;
|
||||
|
||||
if (listStr.indexOf(",") < 0) {
|
||||
// Common case: only one entry, skip split() overhead.
|
||||
values = new int[1];
|
||||
values[0] = parseIntRange(listStr, minVal, maxVal, allowZero);
|
||||
} else {
|
||||
String[] valueStrs = listStr.split(",");
|
||||
int len = valueStrs.length;
|
||||
values = new int[len];
|
||||
for (int i = 0; i < len; i++) {
|
||||
values[i] = parseIntRange(valueStrs[i], minVal, maxVal, allowZero);
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
/** parses FREQ={SECONDLY,MINUTELY,...} */
|
||||
private static class ParseFreq extends PartParser {
|
||||
@Override public int parsePart(String value, EventRecurrence er) {
|
||||
Integer freq = sParseFreqMap.get(value);
|
||||
if (freq == null) {
|
||||
throw new InvalidFormatException("Invalid FREQ value: " + value);
|
||||
}
|
||||
er.freq = freq;
|
||||
return PARSED_FREQ;
|
||||
}
|
||||
}
|
||||
/** parses UNTIL=enddate, e.g. "19970829T021400" */
|
||||
private static class ParseUntil extends PartParser {
|
||||
@Override public int parsePart(String value, EventRecurrence er) {
|
||||
if (VALIDATE_UNTIL) {
|
||||
try {
|
||||
// Parse the time to validate it. The result isn't retained.
|
||||
Time until = new Time();
|
||||
until.parse(value);
|
||||
} catch (TimeFormatException tfe) {
|
||||
throw new InvalidFormatException("Invalid UNTIL value: " + value);
|
||||
}
|
||||
}
|
||||
er.until = value;
|
||||
return PARSED_UNTIL;
|
||||
}
|
||||
}
|
||||
/** parses COUNT=[non-negative-integer] */
|
||||
private static class ParseCount extends PartParser {
|
||||
@Override public int parsePart(String value, EventRecurrence er) {
|
||||
er.count = parseIntRange(value, 0, Integer.MAX_VALUE, true);
|
||||
return PARSED_COUNT;
|
||||
}
|
||||
}
|
||||
/** parses INTERVAL=[non-negative-integer] */
|
||||
private static class ParseInterval extends PartParser {
|
||||
@Override public int parsePart(String value, EventRecurrence er) {
|
||||
er.interval = parseIntRange(value, 1, Integer.MAX_VALUE, false);
|
||||
return PARSED_INTERVAL;
|
||||
}
|
||||
}
|
||||
/** parses BYSECOND=byseclist */
|
||||
private static class ParseBySecond extends PartParser {
|
||||
@Override public int parsePart(String value, EventRecurrence er) {
|
||||
int[] bysecond = parseNumberList(value, 0, 59, true);
|
||||
er.bysecond = bysecond;
|
||||
er.bysecondCount = bysecond.length;
|
||||
return PARSED_BYSECOND;
|
||||
}
|
||||
}
|
||||
/** parses BYMINUTE=byminlist */
|
||||
private static class ParseByMinute extends PartParser {
|
||||
@Override public int parsePart(String value, EventRecurrence er) {
|
||||
int[] byminute = parseNumberList(value, 0, 59, true);
|
||||
er.byminute = byminute;
|
||||
er.byminuteCount = byminute.length;
|
||||
return PARSED_BYMINUTE;
|
||||
}
|
||||
}
|
||||
/** parses BYHOUR=byhrlist */
|
||||
private static class ParseByHour extends PartParser {
|
||||
@Override public int parsePart(String value, EventRecurrence er) {
|
||||
int[] byhour = parseNumberList(value, 0, 23, true);
|
||||
er.byhour = byhour;
|
||||
er.byhourCount = byhour.length;
|
||||
return PARSED_BYHOUR;
|
||||
}
|
||||
}
|
||||
/** parses BYDAY=bywdaylist, e.g. "1SU,-1SU" */
|
||||
private static class ParseByDay extends PartParser {
|
||||
@Override public int parsePart(String value, EventRecurrence er) {
|
||||
int[] byday;
|
||||
int[] bydayNum;
|
||||
int bydayCount;
|
||||
|
||||
if (value.indexOf(",") < 0) {
|
||||
/* only one entry, skip split() overhead */
|
||||
bydayCount = 1;
|
||||
byday = new int[1];
|
||||
bydayNum = new int[1];
|
||||
parseWday(value, byday, bydayNum, 0);
|
||||
} else {
|
||||
String[] wdays = value.split(",");
|
||||
int len = wdays.length;
|
||||
bydayCount = len;
|
||||
byday = new int[len];
|
||||
bydayNum = new int[len];
|
||||
for (int i = 0; i < len; i++) {
|
||||
parseWday(wdays[i], byday, bydayNum, i);
|
||||
}
|
||||
}
|
||||
er.byday = byday;
|
||||
er.bydayNum = bydayNum;
|
||||
er.bydayCount = bydayCount;
|
||||
return PARSED_BYDAY;
|
||||
}
|
||||
|
||||
/** parses [int]weekday, putting the pieces into parallel array entries */
|
||||
private static void parseWday(String str, int[] byday, int[] bydayNum, int index) {
|
||||
int wdayStrStart = str.length() - 2;
|
||||
String wdayStr;
|
||||
|
||||
if (wdayStrStart > 0) {
|
||||
/* number is included; parse it out and advance to weekday */
|
||||
String numPart = str.substring(0, wdayStrStart);
|
||||
int num = parseIntRange(numPart, -53, 53, false);
|
||||
bydayNum[index] = num;
|
||||
wdayStr = str.substring(wdayStrStart);
|
||||
} else {
|
||||
/* just the weekday string */
|
||||
wdayStr = str;
|
||||
}
|
||||
Integer wday = sParseWeekdayMap.get(wdayStr);
|
||||
if (wday == null) {
|
||||
throw new InvalidFormatException("Invalid BYDAY value: " + str);
|
||||
}
|
||||
byday[index] = wday;
|
||||
}
|
||||
}
|
||||
/** parses BYMONTHDAY=bymodaylist */
|
||||
private static class ParseByMonthDay extends PartParser {
|
||||
@Override public int parsePart(String value, EventRecurrence er) {
|
||||
int[] bymonthday = parseNumberList(value, -31, 31, false);
|
||||
er.bymonthday = bymonthday;
|
||||
er.bymonthdayCount = bymonthday.length;
|
||||
return PARSED_BYMONTHDAY;
|
||||
}
|
||||
}
|
||||
/** parses BYYEARDAY=byyrdaylist */
|
||||
private static class ParseByYearDay extends PartParser {
|
||||
@Override public int parsePart(String value, EventRecurrence er) {
|
||||
int[] byyearday = parseNumberList(value, -366, 366, false);
|
||||
er.byyearday = byyearday;
|
||||
er.byyeardayCount = byyearday.length;
|
||||
return PARSED_BYYEARDAY;
|
||||
}
|
||||
}
|
||||
/** parses BYWEEKNO=bywknolist */
|
||||
private static class ParseByWeekNo extends PartParser {
|
||||
@Override public int parsePart(String value, EventRecurrence er) {
|
||||
int[] byweekno = parseNumberList(value, -53, 53, false);
|
||||
er.byweekno = byweekno;
|
||||
er.byweeknoCount = byweekno.length;
|
||||
return PARSED_BYWEEKNO;
|
||||
}
|
||||
}
|
||||
/** parses BYMONTH=bymolist */
|
||||
private static class ParseByMonth extends PartParser {
|
||||
@Override public int parsePart(String value, EventRecurrence er) {
|
||||
int[] bymonth = parseNumberList(value, 1, 12, false);
|
||||
er.bymonth = bymonth;
|
||||
er.bymonthCount = bymonth.length;
|
||||
return PARSED_BYMONTH;
|
||||
}
|
||||
}
|
||||
/** parses BYSETPOS=bysplist */
|
||||
private static class ParseBySetPos extends PartParser {
|
||||
@Override public int parsePart(String value, EventRecurrence er) {
|
||||
int[] bysetpos = parseNumberList(value, Integer.MIN_VALUE, Integer.MAX_VALUE, true);
|
||||
er.bysetpos = bysetpos;
|
||||
er.bysetposCount = bysetpos.length;
|
||||
return PARSED_BYSETPOS;
|
||||
}
|
||||
}
|
||||
/** parses WKST={SU,MO,...} */
|
||||
private static class ParseWkst extends PartParser {
|
||||
@Override public int parsePart(String value, EventRecurrence er) {
|
||||
Integer wkst = sParseWeekdayMap.get(value);
|
||||
if (wkst == null) {
|
||||
throw new InvalidFormatException("Invalid WKST value: " + value);
|
||||
}
|
||||
er.wkst = wkst;
|
||||
return PARSED_WKST;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,660 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.pim;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Parses RFC 2445 iCalendar objects.
|
||||
*/
|
||||
public class ICalendar {
|
||||
|
||||
private static final String TAG = "Sync";
|
||||
|
||||
// TODO: keep track of VEVENT, VTODO, VJOURNAL, VFREEBUSY, VTIMEZONE, VALARM
|
||||
// components, by type field or by subclass? subclass would allow us to
|
||||
// enforce grammars.
|
||||
|
||||
/**
|
||||
* Exception thrown when an iCalendar object has invalid syntax.
|
||||
*/
|
||||
public static class FormatException extends Exception {
|
||||
public FormatException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public FormatException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public FormatException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A component within an iCalendar (VEVENT, VTODO, VJOURNAL, VFEEBUSY,
|
||||
* VTIMEZONE, VALARM).
|
||||
*/
|
||||
public static class Component {
|
||||
|
||||
// components
|
||||
private static final String BEGIN = "BEGIN";
|
||||
private static final String END = "END";
|
||||
private static final String NEWLINE = "\n";
|
||||
public static final String VCALENDAR = "VCALENDAR";
|
||||
public static final String VEVENT = "VEVENT";
|
||||
public static final String VTODO = "VTODO";
|
||||
public static final String VJOURNAL = "VJOURNAL";
|
||||
public static final String VFREEBUSY = "VFREEBUSY";
|
||||
public static final String VTIMEZONE = "VTIMEZONE";
|
||||
public static final String VALARM = "VALARM";
|
||||
|
||||
private final String mName;
|
||||
private final Component mParent; // see if we can get rid of this
|
||||
private LinkedList<Component> mChildren = null;
|
||||
private final LinkedHashMap<String, ArrayList<Property>> mPropsMap =
|
||||
new LinkedHashMap<String, ArrayList<Property>>();
|
||||
|
||||
/**
|
||||
* Creates a new component with the provided name.
|
||||
* @param name The name of the component.
|
||||
*/
|
||||
public Component(String name, Component parent) {
|
||||
mName = name;
|
||||
mParent = parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the component.
|
||||
* @return The name of the component.
|
||||
*/
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parent of this component.
|
||||
* @return The parent of this component.
|
||||
*/
|
||||
public Component getParent() {
|
||||
return mParent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper that lazily gets/creates the list of children.
|
||||
* @return The list of children.
|
||||
*/
|
||||
protected LinkedList<Component> getOrCreateChildren() {
|
||||
if (mChildren == null) {
|
||||
mChildren = new LinkedList<Component>();
|
||||
}
|
||||
return mChildren;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a child component to this component.
|
||||
* @param child The child component.
|
||||
*/
|
||||
public void addChild(Component child) {
|
||||
getOrCreateChildren().add(child);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of the Component children of this component. May be
|
||||
* null, if there are no children.
|
||||
*
|
||||
* @return A list of the children.
|
||||
*/
|
||||
public List<Component> getComponents() {
|
||||
return mChildren;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a Property to this component.
|
||||
* @param prop
|
||||
*/
|
||||
public void addProperty(Property prop) {
|
||||
String name= prop.getName();
|
||||
ArrayList<Property> props = mPropsMap.get(name);
|
||||
if (props == null) {
|
||||
props = new ArrayList<Property>();
|
||||
mPropsMap.put(name, props);
|
||||
}
|
||||
props.add(prop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of the property names within this component.
|
||||
* @return A set of property names within this component.
|
||||
*/
|
||||
public Set<String> getPropertyNames() {
|
||||
return mPropsMap.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of properties with the specified name. Returns null
|
||||
* if there are no such properties.
|
||||
* @param name The name of the property that should be returned.
|
||||
* @return A list of properties with the requested name.
|
||||
*/
|
||||
public List<Property> getProperties(String name) {
|
||||
return mPropsMap.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first property with the specified name. Returns null
|
||||
* if there is no such property.
|
||||
* @param name The name of the property that should be returned.
|
||||
* @return The first property with the specified name.
|
||||
*/
|
||||
public Property getFirstProperty(String name) {
|
||||
List<Property> props = mPropsMap.get(name);
|
||||
if (props == null || props.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
return props.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
toString(sb);
|
||||
sb.append(NEWLINE);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method that appends this component to a StringBuilder. The
|
||||
* caller is responsible for appending a newline at the end of the
|
||||
* component.
|
||||
*/
|
||||
public void toString(StringBuilder sb) {
|
||||
sb.append(BEGIN);
|
||||
sb.append(":");
|
||||
sb.append(mName);
|
||||
sb.append(NEWLINE);
|
||||
|
||||
// append the properties
|
||||
for (String propertyName : getPropertyNames()) {
|
||||
for (Property property : getProperties(propertyName)) {
|
||||
property.toString(sb);
|
||||
sb.append(NEWLINE);
|
||||
}
|
||||
}
|
||||
|
||||
// append the sub-components
|
||||
if (mChildren != null) {
|
||||
for (Component component : mChildren) {
|
||||
component.toString(sb);
|
||||
sb.append(NEWLINE);
|
||||
}
|
||||
}
|
||||
|
||||
sb.append(END);
|
||||
sb.append(":");
|
||||
sb.append(mName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A property within an iCalendar component (e.g., DTSTART, DTEND, etc.,
|
||||
* within a VEVENT).
|
||||
*/
|
||||
public static class Property {
|
||||
// properties
|
||||
// TODO: do we want to list these here? the complete list is long.
|
||||
public static final String DTSTART = "DTSTART";
|
||||
public static final String DTEND = "DTEND";
|
||||
public static final String DURATION = "DURATION";
|
||||
public static final String RRULE = "RRULE";
|
||||
public static final String RDATE = "RDATE";
|
||||
public static final String EXRULE = "EXRULE";
|
||||
public static final String EXDATE = "EXDATE";
|
||||
// ... need to add more.
|
||||
|
||||
private final String mName;
|
||||
private LinkedHashMap<String, ArrayList<Parameter>> mParamsMap =
|
||||
new LinkedHashMap<String, ArrayList<Parameter>>();
|
||||
private String mValue; // TODO: make this final?
|
||||
|
||||
/**
|
||||
* Creates a new property with the provided name.
|
||||
* @param name The name of the property.
|
||||
*/
|
||||
public Property(String name) {
|
||||
mName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new property with the provided name and value.
|
||||
* @param name The name of the property.
|
||||
* @param value The value of the property.
|
||||
*/
|
||||
public Property(String name, String value) {
|
||||
mName = name;
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the property.
|
||||
* @return The name of the property.
|
||||
*/
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of this property.
|
||||
* @return The value of this property.
|
||||
*/
|
||||
public String getValue() {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of this property.
|
||||
* @param value The desired value for this property.
|
||||
*/
|
||||
public void setValue(String value) {
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a {@link Parameter} to this property.
|
||||
* @param param The parameter that should be added.
|
||||
*/
|
||||
public void addParameter(Parameter param) {
|
||||
ArrayList<Parameter> params = mParamsMap.get(param.name);
|
||||
if (params == null) {
|
||||
params = new ArrayList<Parameter>();
|
||||
mParamsMap.put(param.name, params);
|
||||
}
|
||||
params.add(param);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of parameter names for this property.
|
||||
* @return The set of parameter names for this property.
|
||||
*/
|
||||
public Set<String> getParameterNames() {
|
||||
return mParamsMap.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of parameters with the specified name. May return
|
||||
* null if there are no such parameters.
|
||||
* @param name The name of the parameters that should be returned.
|
||||
* @return The list of parameters with the specified name.
|
||||
*/
|
||||
public List<Parameter> getParameters(String name) {
|
||||
return mParamsMap.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first parameter with the specified name. May return
|
||||
* nll if there is no such parameter.
|
||||
* @param name The name of the parameter that should be returned.
|
||||
* @return The first parameter with the specified name.
|
||||
*/
|
||||
public Parameter getFirstParameter(String name) {
|
||||
ArrayList<Parameter> params = mParamsMap.get(name);
|
||||
if (params == null || params.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
return params.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
toString(sb);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method that appends this property to a StringBuilder. The
|
||||
* caller is responsible for appending a newline after this property.
|
||||
*/
|
||||
public void toString(StringBuilder sb) {
|
||||
sb.append(mName);
|
||||
Set<String> parameterNames = getParameterNames();
|
||||
for (String parameterName : parameterNames) {
|
||||
for (Parameter param : getParameters(parameterName)) {
|
||||
sb.append(";");
|
||||
param.toString(sb);
|
||||
}
|
||||
}
|
||||
sb.append(":");
|
||||
sb.append(mValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A parameter defined for an iCalendar property.
|
||||
*/
|
||||
// TODO: make this a proper class rather than a struct?
|
||||
public static class Parameter {
|
||||
public String name;
|
||||
public String value;
|
||||
|
||||
/**
|
||||
* Creates a new empty parameter.
|
||||
*/
|
||||
public Parameter() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new parameter with the specified name and value.
|
||||
* @param name The name of the parameter.
|
||||
* @param value The value of the parameter.
|
||||
*/
|
||||
public Parameter(String name, String value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
toString(sb);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method that appends this parameter to a StringBuilder.
|
||||
*/
|
||||
public void toString(StringBuilder sb) {
|
||||
sb.append(name);
|
||||
sb.append("=");
|
||||
sb.append(value);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ParserState {
|
||||
// public int lineNumber = 0;
|
||||
public String line; // TODO: just point to original text
|
||||
public int index;
|
||||
}
|
||||
|
||||
// use factory method
|
||||
private ICalendar() {
|
||||
}
|
||||
|
||||
// TODO: get rid of this -- handle all of the parsing in one pass through
|
||||
// the text.
|
||||
private static String normalizeText(String text) {
|
||||
// it's supposed to be \r\n, but not everyone does that
|
||||
text = text.replaceAll("\r\n", "\n");
|
||||
text = text.replaceAll("\r", "\n");
|
||||
|
||||
// we deal with line folding, by replacing all "\n " strings
|
||||
// with nothing. The RFC specifies "\r\n " to be folded, but
|
||||
// we handle "\n " and "\r " too because we can get those.
|
||||
text = text.replaceAll("\n ", "");
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses text into an iCalendar component. Parses into the provided
|
||||
* component, if not null, or parses into a new component. In the latter
|
||||
* case, expects a BEGIN as the first line. Returns the provided or newly
|
||||
* created top-level component.
|
||||
*/
|
||||
// TODO: use an index into the text, so we can make this a recursive
|
||||
// function?
|
||||
private static Component parseComponentImpl(Component component,
|
||||
String text)
|
||||
throws FormatException {
|
||||
Component current = component;
|
||||
ParserState state = new ParserState();
|
||||
state.index = 0;
|
||||
|
||||
// split into lines
|
||||
String[] lines = text.split("\n");
|
||||
|
||||
// each line is of the format:
|
||||
// name *(";" param) ":" value
|
||||
for (String line : lines) {
|
||||
try {
|
||||
current = parseLine(line, state, current);
|
||||
// if the provided component was null, we will return the root
|
||||
// NOTE: in this case, if the first line is not a BEGIN, a
|
||||
// FormatException will get thrown.
|
||||
if (component == null) {
|
||||
component = current;
|
||||
}
|
||||
} catch (FormatException fe) {
|
||||
if (false) {
|
||||
Log.v(TAG, "Cannot parse " + line, fe);
|
||||
}
|
||||
// for now, we ignore the parse error. Google Calendar seems
|
||||
// to be emitting some misformatted iCalendar objects.
|
||||
}
|
||||
continue;
|
||||
}
|
||||
return component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a line into the provided component. Creates a new component if
|
||||
* the line is a BEGIN, adding the newly created component to the provided
|
||||
* parent. Returns whatever component is the current one (to which new
|
||||
* properties will be added) in the parse.
|
||||
*/
|
||||
private static Component parseLine(String line, ParserState state,
|
||||
Component component)
|
||||
throws FormatException {
|
||||
state.line = line;
|
||||
int len = state.line.length();
|
||||
|
||||
// grab the name
|
||||
char c = 0;
|
||||
for (state.index = 0; state.index < len; ++state.index) {
|
||||
c = line.charAt(state.index);
|
||||
if (c == ';' || c == ':') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
String name = line.substring(0, state.index);
|
||||
|
||||
if (component == null) {
|
||||
if (!Component.BEGIN.equals(name)) {
|
||||
throw new FormatException("Expected BEGIN");
|
||||
}
|
||||
}
|
||||
|
||||
Property property;
|
||||
if (Component.BEGIN.equals(name)) {
|
||||
// start a new component
|
||||
String componentName = extractValue(state);
|
||||
Component child = new Component(componentName, component);
|
||||
if (component != null) {
|
||||
component.addChild(child);
|
||||
}
|
||||
return child;
|
||||
} else if (Component.END.equals(name)) {
|
||||
// finish the current component
|
||||
String componentName = extractValue(state);
|
||||
if (component == null ||
|
||||
!componentName.equals(component.getName())) {
|
||||
throw new FormatException("Unexpected END " + componentName);
|
||||
}
|
||||
return component.getParent();
|
||||
} else {
|
||||
property = new Property(name);
|
||||
}
|
||||
|
||||
if (c == ';') {
|
||||
Parameter parameter = null;
|
||||
while ((parameter = extractParameter(state)) != null) {
|
||||
property.addParameter(parameter);
|
||||
}
|
||||
}
|
||||
String value = extractValue(state);
|
||||
property.setValue(value);
|
||||
component.addProperty(property);
|
||||
return component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the value ":..." on the current line. The first character must
|
||||
* be a ':'.
|
||||
*/
|
||||
private static String extractValue(ParserState state)
|
||||
throws FormatException {
|
||||
String line = state.line;
|
||||
if (state.index >= line.length() || line.charAt(state.index) != ':') {
|
||||
throw new FormatException("Expected ':' before end of line in "
|
||||
+ line);
|
||||
}
|
||||
String value = line.substring(state.index + 1);
|
||||
state.index = line.length() - 1;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the next parameter from the line, if any. If there are no more
|
||||
* parameters, returns null.
|
||||
*/
|
||||
private static Parameter extractParameter(ParserState state)
|
||||
throws FormatException {
|
||||
String text = state.line;
|
||||
int len = text.length();
|
||||
Parameter parameter = null;
|
||||
int startIndex = -1;
|
||||
int equalIndex = -1;
|
||||
while (state.index < len) {
|
||||
char c = text.charAt(state.index);
|
||||
if (c == ':') {
|
||||
if (parameter != null) {
|
||||
if (equalIndex == -1) {
|
||||
throw new FormatException("Expected '=' within "
|
||||
+ "parameter in " + text);
|
||||
}
|
||||
parameter.value = text.substring(equalIndex + 1,
|
||||
state.index);
|
||||
}
|
||||
return parameter; // may be null
|
||||
} else if (c == ';') {
|
||||
if (parameter != null) {
|
||||
if (equalIndex == -1) {
|
||||
throw new FormatException("Expected '=' within "
|
||||
+ "parameter in " + text);
|
||||
}
|
||||
parameter.value = text.substring(equalIndex + 1,
|
||||
state.index);
|
||||
return parameter;
|
||||
} else {
|
||||
parameter = new Parameter();
|
||||
startIndex = state.index;
|
||||
}
|
||||
} else if (c == '=') {
|
||||
equalIndex = state.index;
|
||||
if ((parameter == null) || (startIndex == -1)) {
|
||||
throw new FormatException("Expected ';' before '=' in "
|
||||
+ text);
|
||||
}
|
||||
parameter.name = text.substring(startIndex + 1, equalIndex);
|
||||
} else if (c == '"') {
|
||||
if (parameter == null) {
|
||||
throw new FormatException("Expected parameter before '\"' in " + text);
|
||||
}
|
||||
if (equalIndex == -1) {
|
||||
throw new FormatException("Expected '=' within parameter in " + text);
|
||||
}
|
||||
if (state.index > equalIndex + 1) {
|
||||
throw new FormatException("Parameter value cannot contain a '\"' in " + text);
|
||||
}
|
||||
final int endQuote = text.indexOf('"', state.index + 1);
|
||||
if (endQuote < 0) {
|
||||
throw new FormatException("Expected closing '\"' in " + text);
|
||||
}
|
||||
parameter.value = text.substring(state.index + 1, endQuote);
|
||||
state.index = endQuote + 1;
|
||||
return parameter;
|
||||
}
|
||||
++state.index;
|
||||
}
|
||||
throw new FormatException("Expected ':' before end of line in " + text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the provided text into an iCalendar object. The top-level
|
||||
* component must be of type VCALENDAR.
|
||||
* @param text The text to be parsed.
|
||||
* @return The top-level VCALENDAR component.
|
||||
* @throws FormatException Thrown if the text could not be parsed into an
|
||||
* iCalendar VCALENDAR object.
|
||||
*/
|
||||
public static Component parseCalendar(String text) throws FormatException {
|
||||
Component calendar = parseComponent(null, text);
|
||||
if (calendar == null || !Component.VCALENDAR.equals(calendar.getName())) {
|
||||
throw new FormatException("Expected " + Component.VCALENDAR);
|
||||
}
|
||||
return calendar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the provided text into an iCalendar event. The top-level
|
||||
* component must be of type VEVENT.
|
||||
* @param text The text to be parsed.
|
||||
* @return The top-level VEVENT component.
|
||||
* @throws FormatException Thrown if the text could not be parsed into an
|
||||
* iCalendar VEVENT.
|
||||
*/
|
||||
public static Component parseEvent(String text) throws FormatException {
|
||||
Component event = parseComponent(null, text);
|
||||
if (event == null || !Component.VEVENT.equals(event.getName())) {
|
||||
throw new FormatException("Expected " + Component.VEVENT);
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the provided text into an iCalendar component.
|
||||
* @param text The text to be parsed.
|
||||
* @return The top-level component.
|
||||
* @throws FormatException Thrown if the text could not be parsed into an
|
||||
* iCalendar component.
|
||||
*/
|
||||
public static Component parseComponent(String text) throws FormatException {
|
||||
return parseComponent(null, text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the provided text, adding to the provided component.
|
||||
* @param component The component to which the parsed iCalendar data should
|
||||
* be added.
|
||||
* @param text The text to be parsed.
|
||||
* @return The top-level component.
|
||||
* @throws FormatException Thrown if the text could not be parsed as an
|
||||
* iCalendar object.
|
||||
*/
|
||||
public static Component parseComponent(Component component, String text)
|
||||
throws FormatException {
|
||||
text = normalizeText(text);
|
||||
return parseComponentImpl(component, text);
|
||||
}
|
||||
}
|
||||
@@ -1,511 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.pim;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.provider.CalendarContract;
|
||||
import android.text.TextUtils;
|
||||
import android.text.format.Time;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Basic information about a recurrence, following RFC 2445 Section 4.8.5.
|
||||
* Contains the RRULEs, RDATE, EXRULEs, and EXDATE properties.
|
||||
*/
|
||||
public class RecurrenceSet {
|
||||
|
||||
private final static String TAG = "CalendarProvider";
|
||||
|
||||
private final static String RULE_SEPARATOR = "\n";
|
||||
private final static String FOLDING_SEPARATOR = "\n ";
|
||||
|
||||
// TODO: make these final?
|
||||
public EventRecurrence[] rrules = null;
|
||||
public long[] rdates = null;
|
||||
public EventRecurrence[] exrules = null;
|
||||
public long[] exdates = null;
|
||||
|
||||
/**
|
||||
* Creates a new RecurrenceSet from information stored in the
|
||||
* events table in the CalendarProvider.
|
||||
* @param values The values retrieved from the Events table.
|
||||
*/
|
||||
public RecurrenceSet(ContentValues values)
|
||||
throws EventRecurrence.InvalidFormatException {
|
||||
String rruleStr = values.getAsString(CalendarContract.Events.RRULE);
|
||||
String rdateStr = values.getAsString(CalendarContract.Events.RDATE);
|
||||
String exruleStr = values.getAsString(CalendarContract.Events.EXRULE);
|
||||
String exdateStr = values.getAsString(CalendarContract.Events.EXDATE);
|
||||
init(rruleStr, rdateStr, exruleStr, exdateStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new RecurrenceSet from information stored in a database
|
||||
* {@link Cursor} pointing to the events table in the
|
||||
* CalendarProvider. The cursor must contain the RRULE, RDATE, EXRULE,
|
||||
* and EXDATE columns.
|
||||
*
|
||||
* @param cursor The cursor containing the RRULE, RDATE, EXRULE, and EXDATE
|
||||
* columns.
|
||||
*/
|
||||
public RecurrenceSet(Cursor cursor)
|
||||
throws EventRecurrence.InvalidFormatException {
|
||||
int rruleColumn = cursor.getColumnIndex(CalendarContract.Events.RRULE);
|
||||
int rdateColumn = cursor.getColumnIndex(CalendarContract.Events.RDATE);
|
||||
int exruleColumn = cursor.getColumnIndex(CalendarContract.Events.EXRULE);
|
||||
int exdateColumn = cursor.getColumnIndex(CalendarContract.Events.EXDATE);
|
||||
String rruleStr = cursor.getString(rruleColumn);
|
||||
String rdateStr = cursor.getString(rdateColumn);
|
||||
String exruleStr = cursor.getString(exruleColumn);
|
||||
String exdateStr = cursor.getString(exdateColumn);
|
||||
init(rruleStr, rdateStr, exruleStr, exdateStr);
|
||||
}
|
||||
|
||||
public RecurrenceSet(String rruleStr, String rdateStr,
|
||||
String exruleStr, String exdateStr)
|
||||
throws EventRecurrence.InvalidFormatException {
|
||||
init(rruleStr, rdateStr, exruleStr, exdateStr);
|
||||
}
|
||||
|
||||
private void init(String rruleStr, String rdateStr,
|
||||
String exruleStr, String exdateStr)
|
||||
throws EventRecurrence.InvalidFormatException {
|
||||
if (!TextUtils.isEmpty(rruleStr) || !TextUtils.isEmpty(rdateStr)) {
|
||||
|
||||
if (!TextUtils.isEmpty(rruleStr)) {
|
||||
String[] rruleStrs = rruleStr.split(RULE_SEPARATOR);
|
||||
rrules = new EventRecurrence[rruleStrs.length];
|
||||
for (int i = 0; i < rruleStrs.length; ++i) {
|
||||
EventRecurrence rrule = new EventRecurrence();
|
||||
rrule.parse(rruleStrs[i]);
|
||||
rrules[i] = rrule;
|
||||
}
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(rdateStr)) {
|
||||
rdates = parseRecurrenceDates(rdateStr);
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(exruleStr)) {
|
||||
String[] exruleStrs = exruleStr.split(RULE_SEPARATOR);
|
||||
exrules = new EventRecurrence[exruleStrs.length];
|
||||
for (int i = 0; i < exruleStrs.length; ++i) {
|
||||
EventRecurrence exrule = new EventRecurrence();
|
||||
exrule.parse(exruleStr);
|
||||
exrules[i] = exrule;
|
||||
}
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(exdateStr)) {
|
||||
exdates = parseRecurrenceDates(exdateStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not a recurrence is defined in this RecurrenceSet.
|
||||
* @return Whether or not a recurrence is defined in this RecurrenceSet.
|
||||
*/
|
||||
public boolean hasRecurrence() {
|
||||
return (rrules != null || rdates != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the provided RDATE or EXDATE string into an array of longs
|
||||
* representing each date/time in the recurrence.
|
||||
* @param recurrence The recurrence to be parsed.
|
||||
* @return The list of date/times.
|
||||
*/
|
||||
public static long[] parseRecurrenceDates(String recurrence) {
|
||||
// TODO: use "local" time as the default. will need to handle times
|
||||
// that end in "z" (UTC time) explicitly at that point.
|
||||
String tz = Time.TIMEZONE_UTC;
|
||||
int tzidx = recurrence.indexOf(";");
|
||||
if (tzidx != -1) {
|
||||
tz = recurrence.substring(0, tzidx);
|
||||
recurrence = recurrence.substring(tzidx + 1);
|
||||
}
|
||||
Time time = new Time(tz);
|
||||
String[] rawDates = recurrence.split(",");
|
||||
int n = rawDates.length;
|
||||
long[] dates = new long[n];
|
||||
for (int i = 0; i<n; ++i) {
|
||||
// The timezone is updated to UTC if the time string specified 'Z'.
|
||||
time.parse(rawDates[i]);
|
||||
dates[i] = time.toMillis(false /* use isDst */);
|
||||
time.timezone = tz;
|
||||
}
|
||||
return dates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the database map of values with the appropriate RRULE, RDATE,
|
||||
* EXRULE, and EXDATE values extracted from the parsed iCalendar component.
|
||||
* @param component The iCalendar component containing the desired
|
||||
* recurrence specification.
|
||||
* @param values The db values that should be updated.
|
||||
* @return true if the component contained the necessary information
|
||||
* to specify a recurrence. The required fields are DTSTART,
|
||||
* one of DTEND/DURATION, and one of RRULE/RDATE. Returns false if
|
||||
* there was an error, including if the date is out of range.
|
||||
*/
|
||||
public static boolean populateContentValues(ICalendar.Component component,
|
||||
ContentValues values) {
|
||||
ICalendar.Property dtstartProperty =
|
||||
component.getFirstProperty("DTSTART");
|
||||
String dtstart = dtstartProperty.getValue();
|
||||
ICalendar.Parameter tzidParam =
|
||||
dtstartProperty.getFirstParameter("TZID");
|
||||
// NOTE: the timezone may be null, if this is a floating time.
|
||||
String tzid = tzidParam == null ? null : tzidParam.value;
|
||||
Time start = new Time(tzidParam == null ? Time.TIMEZONE_UTC : tzid);
|
||||
boolean inUtc = start.parse(dtstart);
|
||||
boolean allDay = start.allDay;
|
||||
|
||||
// We force TimeZone to UTC for "all day recurring events" as the server is sending no
|
||||
// TimeZone in DTSTART for them
|
||||
if (inUtc || allDay) {
|
||||
tzid = Time.TIMEZONE_UTC;
|
||||
}
|
||||
|
||||
String duration = computeDuration(start, component);
|
||||
String rrule = flattenProperties(component, "RRULE");
|
||||
String rdate = extractDates(component.getFirstProperty("RDATE"));
|
||||
String exrule = flattenProperties(component, "EXRULE");
|
||||
String exdate = extractDates(component.getFirstProperty("EXDATE"));
|
||||
|
||||
if ((TextUtils.isEmpty(dtstart))||
|
||||
(TextUtils.isEmpty(duration))||
|
||||
((TextUtils.isEmpty(rrule))&&
|
||||
(TextUtils.isEmpty(rdate)))) {
|
||||
if (false) {
|
||||
Log.d(TAG, "Recurrence missing DTSTART, DTEND/DURATION, "
|
||||
+ "or RRULE/RDATE: "
|
||||
+ component.toString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (allDay) {
|
||||
start.timezone = Time.TIMEZONE_UTC;
|
||||
}
|
||||
long millis = start.toMillis(false /* use isDst */);
|
||||
values.put(CalendarContract.Events.DTSTART, millis);
|
||||
if (millis == -1) {
|
||||
if (false) {
|
||||
Log.d(TAG, "DTSTART is out of range: " + component.toString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
values.put(CalendarContract.Events.RRULE, rrule);
|
||||
values.put(CalendarContract.Events.RDATE, rdate);
|
||||
values.put(CalendarContract.Events.EXRULE, exrule);
|
||||
values.put(CalendarContract.Events.EXDATE, exdate);
|
||||
values.put(CalendarContract.Events.EVENT_TIMEZONE, tzid);
|
||||
values.put(CalendarContract.Events.DURATION, duration);
|
||||
values.put(CalendarContract.Events.ALL_DAY, allDay ? 1 : 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
// This can be removed when the old CalendarSyncAdapter is removed.
|
||||
public static boolean populateComponent(Cursor cursor,
|
||||
ICalendar.Component component) {
|
||||
|
||||
int dtstartColumn = cursor.getColumnIndex(CalendarContract.Events.DTSTART);
|
||||
int durationColumn = cursor.getColumnIndex(CalendarContract.Events.DURATION);
|
||||
int tzidColumn = cursor.getColumnIndex(CalendarContract.Events.EVENT_TIMEZONE);
|
||||
int rruleColumn = cursor.getColumnIndex(CalendarContract.Events.RRULE);
|
||||
int rdateColumn = cursor.getColumnIndex(CalendarContract.Events.RDATE);
|
||||
int exruleColumn = cursor.getColumnIndex(CalendarContract.Events.EXRULE);
|
||||
int exdateColumn = cursor.getColumnIndex(CalendarContract.Events.EXDATE);
|
||||
int allDayColumn = cursor.getColumnIndex(CalendarContract.Events.ALL_DAY);
|
||||
|
||||
|
||||
long dtstart = -1;
|
||||
if (!cursor.isNull(dtstartColumn)) {
|
||||
dtstart = cursor.getLong(dtstartColumn);
|
||||
}
|
||||
String duration = cursor.getString(durationColumn);
|
||||
String tzid = cursor.getString(tzidColumn);
|
||||
String rruleStr = cursor.getString(rruleColumn);
|
||||
String rdateStr = cursor.getString(rdateColumn);
|
||||
String exruleStr = cursor.getString(exruleColumn);
|
||||
String exdateStr = cursor.getString(exdateColumn);
|
||||
boolean allDay = cursor.getInt(allDayColumn) == 1;
|
||||
|
||||
if ((dtstart == -1) ||
|
||||
(TextUtils.isEmpty(duration))||
|
||||
((TextUtils.isEmpty(rruleStr))&&
|
||||
(TextUtils.isEmpty(rdateStr)))) {
|
||||
// no recurrence.
|
||||
return false;
|
||||
}
|
||||
|
||||
ICalendar.Property dtstartProp = new ICalendar.Property("DTSTART");
|
||||
Time dtstartTime = null;
|
||||
if (!TextUtils.isEmpty(tzid)) {
|
||||
if (!allDay) {
|
||||
dtstartProp.addParameter(new ICalendar.Parameter("TZID", tzid));
|
||||
}
|
||||
dtstartTime = new Time(tzid);
|
||||
} else {
|
||||
// use the "floating" timezone
|
||||
dtstartTime = new Time(Time.TIMEZONE_UTC);
|
||||
}
|
||||
|
||||
dtstartTime.set(dtstart);
|
||||
// make sure the time is printed just as a date, if all day.
|
||||
// TODO: android.pim.Time really should take care of this for us.
|
||||
if (allDay) {
|
||||
dtstartProp.addParameter(new ICalendar.Parameter("VALUE", "DATE"));
|
||||
dtstartTime.allDay = true;
|
||||
dtstartTime.hour = 0;
|
||||
dtstartTime.minute = 0;
|
||||
dtstartTime.second = 0;
|
||||
}
|
||||
|
||||
dtstartProp.setValue(dtstartTime.format2445());
|
||||
component.addProperty(dtstartProp);
|
||||
ICalendar.Property durationProp = new ICalendar.Property("DURATION");
|
||||
durationProp.setValue(duration);
|
||||
component.addProperty(durationProp);
|
||||
|
||||
addPropertiesForRuleStr(component, "RRULE", rruleStr);
|
||||
addPropertyForDateStr(component, "RDATE", rdateStr);
|
||||
addPropertiesForRuleStr(component, "EXRULE", exruleStr);
|
||||
addPropertyForDateStr(component, "EXDATE", exdateStr);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean populateComponent(ContentValues values,
|
||||
ICalendar.Component component) {
|
||||
long dtstart = -1;
|
||||
if (values.containsKey(CalendarContract.Events.DTSTART)) {
|
||||
dtstart = values.getAsLong(CalendarContract.Events.DTSTART);
|
||||
}
|
||||
String duration = values.getAsString(CalendarContract.Events.DURATION);
|
||||
String tzid = values.getAsString(CalendarContract.Events.EVENT_TIMEZONE);
|
||||
String rruleStr = values.getAsString(CalendarContract.Events.RRULE);
|
||||
String rdateStr = values.getAsString(CalendarContract.Events.RDATE);
|
||||
String exruleStr = values.getAsString(CalendarContract.Events.EXRULE);
|
||||
String exdateStr = values.getAsString(CalendarContract.Events.EXDATE);
|
||||
Integer allDayInteger = values.getAsInteger(CalendarContract.Events.ALL_DAY);
|
||||
boolean allDay = (null != allDayInteger) ? (allDayInteger == 1) : false;
|
||||
|
||||
if ((dtstart == -1) ||
|
||||
(TextUtils.isEmpty(duration))||
|
||||
((TextUtils.isEmpty(rruleStr))&&
|
||||
(TextUtils.isEmpty(rdateStr)))) {
|
||||
// no recurrence.
|
||||
return false;
|
||||
}
|
||||
|
||||
ICalendar.Property dtstartProp = new ICalendar.Property("DTSTART");
|
||||
Time dtstartTime = null;
|
||||
if (!TextUtils.isEmpty(tzid)) {
|
||||
if (!allDay) {
|
||||
dtstartProp.addParameter(new ICalendar.Parameter("TZID", tzid));
|
||||
}
|
||||
dtstartTime = new Time(tzid);
|
||||
} else {
|
||||
// use the "floating" timezone
|
||||
dtstartTime = new Time(Time.TIMEZONE_UTC);
|
||||
}
|
||||
|
||||
dtstartTime.set(dtstart);
|
||||
// make sure the time is printed just as a date, if all day.
|
||||
// TODO: android.pim.Time really should take care of this for us.
|
||||
if (allDay) {
|
||||
dtstartProp.addParameter(new ICalendar.Parameter("VALUE", "DATE"));
|
||||
dtstartTime.allDay = true;
|
||||
dtstartTime.hour = 0;
|
||||
dtstartTime.minute = 0;
|
||||
dtstartTime.second = 0;
|
||||
}
|
||||
|
||||
dtstartProp.setValue(dtstartTime.format2445());
|
||||
component.addProperty(dtstartProp);
|
||||
ICalendar.Property durationProp = new ICalendar.Property("DURATION");
|
||||
durationProp.setValue(duration);
|
||||
component.addProperty(durationProp);
|
||||
|
||||
addPropertiesForRuleStr(component, "RRULE", rruleStr);
|
||||
addPropertyForDateStr(component, "RDATE", rdateStr);
|
||||
addPropertiesForRuleStr(component, "EXRULE", exruleStr);
|
||||
addPropertyForDateStr(component, "EXDATE", exdateStr);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void addPropertiesForRuleStr(ICalendar.Component component,
|
||||
String propertyName,
|
||||
String ruleStr) {
|
||||
if (TextUtils.isEmpty(ruleStr)) {
|
||||
return;
|
||||
}
|
||||
String[] rrules = getRuleStrings(ruleStr);
|
||||
for (String rrule : rrules) {
|
||||
ICalendar.Property prop = new ICalendar.Property(propertyName);
|
||||
prop.setValue(rrule);
|
||||
component.addProperty(prop);
|
||||
}
|
||||
}
|
||||
|
||||
private static String[] getRuleStrings(String ruleStr) {
|
||||
if (null == ruleStr) {
|
||||
return new String[0];
|
||||
}
|
||||
String unfoldedRuleStr = unfold(ruleStr);
|
||||
String[] split = unfoldedRuleStr.split(RULE_SEPARATOR);
|
||||
int count = split.length;
|
||||
for (int n = 0; n < count; n++) {
|
||||
split[n] = fold(split[n]);
|
||||
}
|
||||
return split;
|
||||
}
|
||||
|
||||
|
||||
private static final Pattern IGNORABLE_ICAL_WHITESPACE_RE =
|
||||
Pattern.compile("(?:\\r\\n?|\\n)[ \t]");
|
||||
|
||||
private static final Pattern FOLD_RE = Pattern.compile(".{75}");
|
||||
|
||||
/**
|
||||
* fold and unfolds ical content lines as per RFC 2445 section 4.1.
|
||||
*
|
||||
* <h3>4.1 Content Lines</h3>
|
||||
*
|
||||
* <p>The iCalendar object is organized into individual lines of text, called
|
||||
* content lines. Content lines are delimited by a line break, which is a CRLF
|
||||
* sequence (US-ASCII decimal 13, followed by US-ASCII decimal 10).
|
||||
*
|
||||
* <p>Lines of text SHOULD NOT be longer than 75 octets, excluding the line
|
||||
* break. Long content lines SHOULD be split into a multiple line
|
||||
* representations using a line "folding" technique. That is, a long line can
|
||||
* be split between any two characters by inserting a CRLF immediately
|
||||
* followed by a single linear white space character (i.e., SPACE, US-ASCII
|
||||
* decimal 32 or HTAB, US-ASCII decimal 9). Any sequence of CRLF followed
|
||||
* immediately by a single linear white space character is ignored (i.e.,
|
||||
* removed) when processing the content type.
|
||||
*/
|
||||
public static String fold(String unfoldedIcalContent) {
|
||||
return FOLD_RE.matcher(unfoldedIcalContent).replaceAll("$0\r\n ");
|
||||
}
|
||||
|
||||
public static String unfold(String foldedIcalContent) {
|
||||
return IGNORABLE_ICAL_WHITESPACE_RE.matcher(
|
||||
foldedIcalContent).replaceAll("");
|
||||
}
|
||||
|
||||
private static void addPropertyForDateStr(ICalendar.Component component,
|
||||
String propertyName,
|
||||
String dateStr) {
|
||||
if (TextUtils.isEmpty(dateStr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ICalendar.Property prop = new ICalendar.Property(propertyName);
|
||||
String tz = null;
|
||||
int tzidx = dateStr.indexOf(";");
|
||||
if (tzidx != -1) {
|
||||
tz = dateStr.substring(0, tzidx);
|
||||
dateStr = dateStr.substring(tzidx + 1);
|
||||
}
|
||||
if (!TextUtils.isEmpty(tz)) {
|
||||
prop.addParameter(new ICalendar.Parameter("TZID", tz));
|
||||
}
|
||||
prop.setValue(dateStr);
|
||||
component.addProperty(prop);
|
||||
}
|
||||
|
||||
private static String computeDuration(Time start,
|
||||
ICalendar.Component component) {
|
||||
// see if a duration is defined
|
||||
ICalendar.Property durationProperty =
|
||||
component.getFirstProperty("DURATION");
|
||||
if (durationProperty != null) {
|
||||
// just return the duration
|
||||
return durationProperty.getValue();
|
||||
}
|
||||
|
||||
// must compute a duration from the DTEND
|
||||
ICalendar.Property dtendProperty =
|
||||
component.getFirstProperty("DTEND");
|
||||
if (dtendProperty == null) {
|
||||
// no DURATION, no DTEND: 0 second duration
|
||||
return "+P0S";
|
||||
}
|
||||
ICalendar.Parameter endTzidParameter =
|
||||
dtendProperty.getFirstParameter("TZID");
|
||||
String endTzid = (endTzidParameter == null)
|
||||
? start.timezone : endTzidParameter.value;
|
||||
|
||||
Time end = new Time(endTzid);
|
||||
end.parse(dtendProperty.getValue());
|
||||
long durationMillis = end.toMillis(false /* use isDst */)
|
||||
- start.toMillis(false /* use isDst */);
|
||||
long durationSeconds = (durationMillis / 1000);
|
||||
if (start.allDay && (durationSeconds % 86400) == 0) {
|
||||
return "P" + (durationSeconds / 86400) + "D"; // Server wants this instead of P86400S
|
||||
} else {
|
||||
return "P" + durationSeconds + "S";
|
||||
}
|
||||
}
|
||||
|
||||
private static String flattenProperties(ICalendar.Component component,
|
||||
String name) {
|
||||
List<ICalendar.Property> properties = component.getProperties(name);
|
||||
if (properties == null || properties.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (properties.size() == 1) {
|
||||
return properties.get(0).getValue();
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
boolean first = true;
|
||||
for (ICalendar.Property property : component.getProperties(name)) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
// TODO: use commas. our RECUR parsing should handle that
|
||||
// anyway.
|
||||
sb.append(RULE_SEPARATOR);
|
||||
}
|
||||
sb.append(property.getValue());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String extractDates(ICalendar.Property recurrence) {
|
||||
if (recurrence == null) {
|
||||
return null;
|
||||
}
|
||||
ICalendar.Parameter tzidParam =
|
||||
recurrence.getFirstParameter("TZID");
|
||||
if (tzidParam != null) {
|
||||
return tzidParam.value + ";" + recurrence.getValue();
|
||||
}
|
||||
return recurrence.getValue();
|
||||
}
|
||||
}
|
||||
@@ -1,753 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2006 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.pim;
|
||||
|
||||
import android.pim.EventRecurrence.InvalidFormatException;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.test.suitebuilder.annotation.Suppress;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Test android.pim.EventRecurrence.
|
||||
*
|
||||
* adb shell am instrument -w -e class android.pim.EventRecurrenceTest \
|
||||
* com.android.frameworks.coretests/android.test.InstrumentationTestRunner
|
||||
*/
|
||||
public class EventRecurrenceTest extends TestCase {
|
||||
|
||||
@SmallTest
|
||||
public void test0() throws Exception {
|
||||
verifyRecurType("FREQ=SECONDLY",
|
||||
/* int freq */ EventRecurrence.SECONDLY,
|
||||
/* String until */ null,
|
||||
/* int count */ 0,
|
||||
/* int interval */ 0,
|
||||
/* int[] bysecond */ null,
|
||||
/* int[] byminute */ null,
|
||||
/* int[] byhour */ null,
|
||||
/* int[] byday */ null,
|
||||
/* int[] bydayNum */ null,
|
||||
/* int[] bymonthday */ null,
|
||||
/* int[] byyearday */ null,
|
||||
/* int[] byweekno */ null,
|
||||
/* int[] bymonth */ null,
|
||||
/* int[] bysetpos */ null,
|
||||
/* int wkst */ EventRecurrence.MO
|
||||
);
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void test1() throws Exception {
|
||||
verifyRecurType("FREQ=MINUTELY",
|
||||
/* int freq */ EventRecurrence.MINUTELY,
|
||||
/* String until */ null,
|
||||
/* int count */ 0,
|
||||
/* int interval */ 0,
|
||||
/* int[] bysecond */ null,
|
||||
/* int[] byminute */ null,
|
||||
/* int[] byhour */ null,
|
||||
/* int[] byday */ null,
|
||||
/* int[] bydayNum */ null,
|
||||
/* int[] bymonthday */ null,
|
||||
/* int[] byyearday */ null,
|
||||
/* int[] byweekno */ null,
|
||||
/* int[] bymonth */ null,
|
||||
/* int[] bysetpos */ null,
|
||||
/* int wkst */ EventRecurrence.MO
|
||||
);
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void test2() throws Exception {
|
||||
verifyRecurType("FREQ=HOURLY",
|
||||
/* int freq */ EventRecurrence.HOURLY,
|
||||
/* String until */ null,
|
||||
/* int count */ 0,
|
||||
/* int interval */ 0,
|
||||
/* int[] bysecond */ null,
|
||||
/* int[] byminute */ null,
|
||||
/* int[] byhour */ null,
|
||||
/* int[] byday */ null,
|
||||
/* int[] bydayNum */ null,
|
||||
/* int[] bymonthday */ null,
|
||||
/* int[] byyearday */ null,
|
||||
/* int[] byweekno */ null,
|
||||
/* int[] bymonth */ null,
|
||||
/* int[] bysetpos */ null,
|
||||
/* int wkst */ EventRecurrence.MO
|
||||
);
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void test3() throws Exception {
|
||||
verifyRecurType("FREQ=DAILY",
|
||||
/* int freq */ EventRecurrence.DAILY,
|
||||
/* String until */ null,
|
||||
/* int count */ 0,
|
||||
/* int interval */ 0,
|
||||
/* int[] bysecond */ null,
|
||||
/* int[] byminute */ null,
|
||||
/* int[] byhour */ null,
|
||||
/* int[] byday */ null,
|
||||
/* int[] bydayNum */ null,
|
||||
/* int[] bymonthday */ null,
|
||||
/* int[] byyearday */ null,
|
||||
/* int[] byweekno */ null,
|
||||
/* int[] bymonth */ null,
|
||||
/* int[] bysetpos */ null,
|
||||
/* int wkst */ EventRecurrence.MO
|
||||
);
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void test4() throws Exception {
|
||||
verifyRecurType("FREQ=WEEKLY",
|
||||
/* int freq */ EventRecurrence.WEEKLY,
|
||||
/* String until */ null,
|
||||
/* int count */ 0,
|
||||
/* int interval */ 0,
|
||||
/* int[] bysecond */ null,
|
||||
/* int[] byminute */ null,
|
||||
/* int[] byhour */ null,
|
||||
/* int[] byday */ null,
|
||||
/* int[] bydayNum */ null,
|
||||
/* int[] bymonthday */ null,
|
||||
/* int[] byyearday */ null,
|
||||
/* int[] byweekno */ null,
|
||||
/* int[] bymonth */ null,
|
||||
/* int[] bysetpos */ null,
|
||||
/* int wkst */ EventRecurrence.MO
|
||||
);
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void test5() throws Exception {
|
||||
verifyRecurType("FREQ=MONTHLY",
|
||||
/* int freq */ EventRecurrence.MONTHLY,
|
||||
/* String until */ null,
|
||||
/* int count */ 0,
|
||||
/* int interval */ 0,
|
||||
/* int[] bysecond */ null,
|
||||
/* int[] byminute */ null,
|
||||
/* int[] byhour */ null,
|
||||
/* int[] byday */ null,
|
||||
/* int[] bydayNum */ null,
|
||||
/* int[] bymonthday */ null,
|
||||
/* int[] byyearday */ null,
|
||||
/* int[] byweekno */ null,
|
||||
/* int[] bymonth */ null,
|
||||
/* int[] bysetpos */ null,
|
||||
/* int wkst */ EventRecurrence.MO
|
||||
);
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void test6() throws Exception {
|
||||
verifyRecurType("FREQ=YEARLY",
|
||||
/* int freq */ EventRecurrence.YEARLY,
|
||||
/* String until */ null,
|
||||
/* int count */ 0,
|
||||
/* int interval */ 0,
|
||||
/* int[] bysecond */ null,
|
||||
/* int[] byminute */ null,
|
||||
/* int[] byhour */ null,
|
||||
/* int[] byday */ null,
|
||||
/* int[] bydayNum */ null,
|
||||
/* int[] bymonthday */ null,
|
||||
/* int[] byyearday */ null,
|
||||
/* int[] byweekno */ null,
|
||||
/* int[] bymonth */ null,
|
||||
/* int[] bysetpos */ null,
|
||||
/* int wkst */ EventRecurrence.MO
|
||||
);
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void test7() throws Exception {
|
||||
// with an until
|
||||
verifyRecurType("FREQ=DAILY;UNTIL=112233T223344Z",
|
||||
/* int freq */ EventRecurrence.DAILY,
|
||||
/* String until */ "112233T223344Z",
|
||||
/* int count */ 0,
|
||||
/* int interval */ 0,
|
||||
/* int[] bysecond */ null,
|
||||
/* int[] byminute */ null,
|
||||
/* int[] byhour */ null,
|
||||
/* int[] byday */ null,
|
||||
/* int[] bydayNum */ null,
|
||||
/* int[] bymonthday */ null,
|
||||
/* int[] byyearday */ null,
|
||||
/* int[] byweekno */ null,
|
||||
/* int[] bymonth */ null,
|
||||
/* int[] bysetpos */ null,
|
||||
/* int wkst */ EventRecurrence.MO
|
||||
);
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void test8() throws Exception {
|
||||
// with a count
|
||||
verifyRecurType("FREQ=DAILY;COUNT=334",
|
||||
/* int freq */ EventRecurrence.DAILY,
|
||||
/* String until */ null,
|
||||
/* int count */ 334,
|
||||
/* int interval */ 0,
|
||||
/* int[] bysecond */ null,
|
||||
/* int[] byminute */ null,
|
||||
/* int[] byhour */ null,
|
||||
/* int[] byday */ null,
|
||||
/* int[] bydayNum */ null,
|
||||
/* int[] bymonthday */ null,
|
||||
/* int[] byyearday */ null,
|
||||
/* int[] byweekno */ null,
|
||||
/* int[] bymonth */ null,
|
||||
/* int[] bysetpos */ null,
|
||||
/* int wkst */ EventRecurrence.MO
|
||||
);
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void test9() throws Exception {
|
||||
// with a count
|
||||
verifyRecurType("FREQ=DAILY;INTERVAL=5000",
|
||||
/* int freq */ EventRecurrence.DAILY,
|
||||
/* String until */ null,
|
||||
/* int count */ 0,
|
||||
/* int interval */ 5000,
|
||||
/* int[] bysecond */ null,
|
||||
/* int[] byminute */ null,
|
||||
/* int[] byhour */ null,
|
||||
/* int[] byday */ null,
|
||||
/* int[] bydayNum */ null,
|
||||
/* int[] bymonthday */ null,
|
||||
/* int[] byyearday */ null,
|
||||
/* int[] byweekno */ null,
|
||||
/* int[] bymonth */ null,
|
||||
/* int[] bysetpos */ null,
|
||||
/* int wkst */ EventRecurrence.MO
|
||||
);
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void test10() throws Exception {
|
||||
// verifyRecurType all of the BY* ones with one element
|
||||
verifyRecurType("FREQ=DAILY"
|
||||
+ ";BYSECOND=0"
|
||||
+ ";BYMINUTE=1"
|
||||
+ ";BYHOUR=2"
|
||||
+ ";BYMONTHDAY=30"
|
||||
+ ";BYYEARDAY=300"
|
||||
+ ";BYWEEKNO=53"
|
||||
+ ";BYMONTH=12"
|
||||
+ ";BYSETPOS=-15"
|
||||
+ ";WKST=SU",
|
||||
/* int freq */ EventRecurrence.DAILY,
|
||||
/* String until */ null,
|
||||
/* int count */ 0,
|
||||
/* int interval */ 0,
|
||||
/* int[] bysecond */ new int[]{0},
|
||||
/* int[] byminute */ new int[]{1},
|
||||
/* int[] byhour */ new int[]{2},
|
||||
/* int[] byday */ null,
|
||||
/* int[] bydayNum */ null,
|
||||
/* int[] bymonthday */ new int[]{30},
|
||||
/* int[] byyearday */ new int[]{300},
|
||||
/* int[] byweekno */ new int[]{53},
|
||||
/* int[] bymonth */ new int[]{12},
|
||||
/* int[] bysetpos */ new int[]{-15},
|
||||
/* int wkst */ EventRecurrence.SU
|
||||
);
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void test11() throws Exception {
|
||||
// verifyRecurType all of the BY* ones with one element
|
||||
verifyRecurType("FREQ=DAILY"
|
||||
+ ";BYSECOND=0,30,59"
|
||||
+ ";BYMINUTE=0,41,59"
|
||||
+ ";BYHOUR=0,4,23"
|
||||
+ ";BYMONTHDAY=-31,-1,1,31"
|
||||
+ ";BYYEARDAY=-366,-1,1,366"
|
||||
+ ";BYWEEKNO=-53,-1,1,53"
|
||||
+ ";BYMONTH=1,12"
|
||||
+ ";BYSETPOS=1,2,3,4,500,10000"
|
||||
+ ";WKST=SU",
|
||||
/* int freq */ EventRecurrence.DAILY,
|
||||
/* String until */ null,
|
||||
/* int count */ 0,
|
||||
/* int interval */ 0,
|
||||
/* int[] bysecond */ new int[]{0, 30, 59},
|
||||
/* int[] byminute */ new int[]{0, 41, 59},
|
||||
/* int[] byhour */ new int[]{0, 4, 23},
|
||||
/* int[] byday */ null,
|
||||
/* int[] bydayNum */ null,
|
||||
/* int[] bymonthday */ new int[]{-31, -1, 1, 31},
|
||||
/* int[] byyearday */ new int[]{-366, -1, 1, 366},
|
||||
/* int[] byweekno */ new int[]{-53, -1, 1, 53},
|
||||
/* int[] bymonth */ new int[]{1, 12},
|
||||
/* int[] bysetpos */ new int[]{1, 2, 3, 4, 500, 10000},
|
||||
/* int wkst */ EventRecurrence.SU
|
||||
);
|
||||
}
|
||||
|
||||
private static class Check {
|
||||
Check(String k, int... v) {
|
||||
key = k;
|
||||
values = v;
|
||||
}
|
||||
|
||||
String key;
|
||||
int[] values;
|
||||
}
|
||||
|
||||
// this is a negative verifyRecurType case to verifyRecurType the range of the numbers accepted
|
||||
@SmallTest
|
||||
public void test12() throws Exception {
|
||||
Check[] checks = new Check[]{
|
||||
new Check("BYSECOND", -100, -1, 60, 100),
|
||||
new Check("BYMINUTE", -100, -1, 60, 100),
|
||||
new Check("BYHOUR", -100, -1, 24, 100),
|
||||
new Check("BYMONTHDAY", -100, -32, 0, 32, 100),
|
||||
new Check("BYYEARDAY", -400, -367, 0, 367, 400),
|
||||
new Check("BYWEEKNO", -100, -54, 0, 54, 100),
|
||||
new Check("BYMONTH", -100, -5, 0, 13, 100)
|
||||
};
|
||||
|
||||
for (Check ck : checks) {
|
||||
for (int n : ck.values) {
|
||||
String recur = "FREQ=DAILY;" + ck.key + "=" + n;
|
||||
try {
|
||||
EventRecurrence er = new EventRecurrence();
|
||||
er.parse(recur);
|
||||
fail("Negative verifyRecurType failed. "
|
||||
+ " parse failed to throw an exception for '"
|
||||
+ recur + "'");
|
||||
} catch (EventRecurrence.InvalidFormatException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// verifyRecurType BYDAY
|
||||
@SmallTest
|
||||
public void test13() throws Exception {
|
||||
verifyRecurType("FREQ=DAILY;BYDAY=1SU,-2MO,+33TU,WE,TH,FR,SA",
|
||||
/* int freq */ EventRecurrence.DAILY,
|
||||
/* String until */ null,
|
||||
/* int count */ 0,
|
||||
/* int interval */ 0,
|
||||
/* int[] bysecond */ null,
|
||||
/* int[] byminute */ null,
|
||||
/* int[] byhour */ null,
|
||||
/* int[] byday */ new int[] {
|
||||
EventRecurrence.SU,
|
||||
EventRecurrence.MO,
|
||||
EventRecurrence.TU,
|
||||
EventRecurrence.WE,
|
||||
EventRecurrence.TH,
|
||||
EventRecurrence.FR,
|
||||
EventRecurrence.SA
|
||||
},
|
||||
/* int[] bydayNum */ new int[]{1, -2, 33, 0, 0, 0, 0},
|
||||
/* int[] bymonthday */ null,
|
||||
/* int[] byyearday */ null,
|
||||
/* int[] byweekno */ null,
|
||||
/* int[] bymonth */ null,
|
||||
/* int[] bysetpos */ null,
|
||||
/* int wkst */ EventRecurrence.MO
|
||||
);
|
||||
}
|
||||
|
||||
@Suppress
|
||||
// Repro bug #2331761 - this should fail because of the last comma into BYDAY
|
||||
public void test14() throws Exception {
|
||||
verifyRecurType("FREQ=WEEKLY;WKST=MO;UNTIL=20100129T130000Z;INTERVAL=1;BYDAY=MO,TU,WE,",
|
||||
/* int freq */ EventRecurrence.WEEKLY,
|
||||
/* String until */ "20100129T130000Z",
|
||||
/* int count */ 0,
|
||||
/* int interval */ 1,
|
||||
/* int[] bysecond */ null,
|
||||
/* int[] byminute */ null,
|
||||
/* int[] byhour */ null,
|
||||
/* int[] byday */ new int[] {
|
||||
EventRecurrence.MO,
|
||||
EventRecurrence.TU,
|
||||
EventRecurrence.WE,
|
||||
},
|
||||
/* int[] bydayNum */ new int[]{0, 0, 0},
|
||||
/* int[] bymonthday */ null,
|
||||
/* int[] byyearday */ null,
|
||||
/* int[] byweekno */ null,
|
||||
/* int[] bymonth */ null,
|
||||
/* int[] bysetpos */ null,
|
||||
/* int wkst */ EventRecurrence.MO
|
||||
);
|
||||
}
|
||||
|
||||
// This test should pass
|
||||
public void test15() throws Exception {
|
||||
verifyRecurType("FREQ=WEEKLY;WKST=MO;UNTIL=20100129T130000Z;INTERVAL=1;"
|
||||
+ "BYDAY=MO,TU,WE,TH,FR,SA,SU",
|
||||
/* int freq */ EventRecurrence.WEEKLY,
|
||||
/* String until */ "20100129T130000Z",
|
||||
/* int count */ 0,
|
||||
/* int interval */ 1,
|
||||
/* int[] bysecond */ null,
|
||||
/* int[] byminute */ null,
|
||||
/* int[] byhour */ null,
|
||||
/* int[] byday */ new int[] {
|
||||
EventRecurrence.MO,
|
||||
EventRecurrence.TU,
|
||||
EventRecurrence.WE,
|
||||
EventRecurrence.TH,
|
||||
EventRecurrence.FR,
|
||||
EventRecurrence.SA,
|
||||
EventRecurrence.SU
|
||||
},
|
||||
/* int[] bydayNum */ new int[]{0, 0, 0, 0, 0, 0, 0},
|
||||
/* int[] bymonthday */ null,
|
||||
/* int[] byyearday */ null,
|
||||
/* int[] byweekno */ null,
|
||||
/* int[] bymonth */ null,
|
||||
/* int[] bysetpos */ null,
|
||||
/* int wkst */ EventRecurrence.MO
|
||||
);
|
||||
}
|
||||
|
||||
// Sample coming from RFC2445
|
||||
public void test16() throws Exception {
|
||||
verifyRecurType("FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1",
|
||||
/* int freq */ EventRecurrence.MONTHLY,
|
||||
/* String until */ null,
|
||||
/* int count */ 0,
|
||||
/* int interval */ 0,
|
||||
/* int[] bysecond */ null,
|
||||
/* int[] byminute */ null,
|
||||
/* int[] byhour */ null,
|
||||
/* int[] byday */ new int[] {
|
||||
EventRecurrence.MO,
|
||||
EventRecurrence.TU,
|
||||
EventRecurrence.WE,
|
||||
EventRecurrence.TH,
|
||||
EventRecurrence.FR
|
||||
},
|
||||
/* int[] bydayNum */ new int[] {0, 0, 0, 0, 0},
|
||||
/* int[] bymonthday */ null,
|
||||
/* int[] byyearday */ null,
|
||||
/* int[] byweekno */ null,
|
||||
/* int[] bymonth */ null,
|
||||
/* int[] bysetpos */ new int[] { -1 },
|
||||
/* int wkst */ EventRecurrence.MO
|
||||
);
|
||||
}
|
||||
|
||||
// Sample coming from RFC2445
|
||||
public void test17() throws Exception {
|
||||
verifyRecurType("FREQ=DAILY;COUNT=10;INTERVAL=2",
|
||||
/* int freq */ EventRecurrence.DAILY,
|
||||
/* String until */ null,
|
||||
/* int count */ 10,
|
||||
/* int interval */ 2,
|
||||
/* int[] bysecond */ null,
|
||||
/* int[] byminute */ null,
|
||||
/* int[] byhour */ null,
|
||||
/* int[] byday */ null,
|
||||
/* int[] bydayNum */ null,
|
||||
/* int[] bymonthday */ null,
|
||||
/* int[] byyearday */ null,
|
||||
/* int[] byweekno */ null,
|
||||
/* int[] bymonth */ null,
|
||||
/* int[] bysetpos */ null,
|
||||
/* int wkst */ EventRecurrence.MO
|
||||
);
|
||||
}
|
||||
|
||||
// Sample coming from RFC2445
|
||||
public void test18() throws Exception {
|
||||
verifyRecurType("FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10",
|
||||
/* int freq */ EventRecurrence.YEARLY,
|
||||
/* String until */ null,
|
||||
/* int count */ 0,
|
||||
/* int interval */ 0,
|
||||
/* int[] bysecond */ null,
|
||||
/* int[] byminute */ null,
|
||||
/* int[] byhour */ null,
|
||||
/* int[] byday */ new int[] {
|
||||
EventRecurrence.SU
|
||||
},
|
||||
/* int[] bydayNum */ new int[] { -1 },
|
||||
/* int[] bymonthday */ null,
|
||||
/* int[] byyearday */ null,
|
||||
/* int[] byweekno */ null,
|
||||
/* int[] bymonth */ new int[] { 10 },
|
||||
/* int[] bysetpos */ null,
|
||||
/* int wkst */ EventRecurrence.MO
|
||||
);
|
||||
}
|
||||
|
||||
// Sample coming from bug #1640517
|
||||
public void test19() throws Exception {
|
||||
verifyRecurType("FREQ=YEARLY;BYMONTH=3;BYDAY=TH",
|
||||
/* int freq */ EventRecurrence.YEARLY,
|
||||
/* String until */ null,
|
||||
/* int count */ 0,
|
||||
/* int interval */ 0,
|
||||
/* int[] bysecond */ null,
|
||||
/* int[] byminute */ null,
|
||||
/* int[] byhour */ null,
|
||||
/* int[] byday */ new int[] {
|
||||
EventRecurrence.TH
|
||||
},
|
||||
/* int[] bydayNum */ new int[] { 0 },
|
||||
/* int[] bymonthday */ null,
|
||||
/* int[] byyearday */ null,
|
||||
/* int[] byweekno */ null,
|
||||
/* int[] bymonth */ new int[] { 3 },
|
||||
/* int[] bysetpos */ null,
|
||||
/* int wkst */ EventRecurrence.MO
|
||||
);
|
||||
}
|
||||
|
||||
// for your copying pleasure
|
||||
public void fakeTestXX() throws Exception {
|
||||
verifyRecurType("FREQ=DAILY;",
|
||||
/* int freq */ EventRecurrence.DAILY,
|
||||
/* String until */ null,
|
||||
/* int count */ 0,
|
||||
/* int interval */ 0,
|
||||
/* int[] bysecond */ null,
|
||||
/* int[] byminute */ null,
|
||||
/* int[] byhour */ null,
|
||||
/* int[] byday */ null,
|
||||
/* int[] bydayNum */ null,
|
||||
/* int[] bymonthday */ null,
|
||||
/* int[] byyearday */ null,
|
||||
/* int[] byweekno */ null,
|
||||
/* int[] bymonth */ null,
|
||||
/* int[] bysetpos */ null,
|
||||
/* int wkst */ EventRecurrence.MO
|
||||
);
|
||||
}
|
||||
|
||||
private static void cmp(int vlen, int[] v, int[] correct, String name) {
|
||||
if ((correct == null && v != null)
|
||||
|| (correct != null && v == null)) {
|
||||
throw new RuntimeException("One is null, one isn't for " + name
|
||||
+ ": correct=" + Arrays.toString(correct)
|
||||
+ " actual=" + Arrays.toString(v));
|
||||
}
|
||||
if ((correct == null && vlen != 0)
|
||||
|| (vlen != (correct == null ? 0 : correct.length))) {
|
||||
throw new RuntimeException("Reported length mismatch for " + name
|
||||
+ ": correct=" + ((correct == null) ? "null" : correct.length)
|
||||
+ " actual=" + vlen);
|
||||
}
|
||||
if (correct == null) {
|
||||
return;
|
||||
}
|
||||
if (v.length < correct.length) {
|
||||
throw new RuntimeException("Array length mismatch for " + name
|
||||
+ ": correct=" + Arrays.toString(correct)
|
||||
+ " actual=" + Arrays.toString(v));
|
||||
}
|
||||
for (int i = 0; i < correct.length; i++) {
|
||||
if (v[i] != correct[i]) {
|
||||
throw new RuntimeException("Array value mismatch for " + name
|
||||
+ ": correct=" + Arrays.toString(correct)
|
||||
+ " actual=" + Arrays.toString(v));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean eq(String a, String b) {
|
||||
if ((a == null && b != null) || (a != null && b == null)) {
|
||||
return false;
|
||||
} else {
|
||||
return a == b || a.equals(b);
|
||||
}
|
||||
}
|
||||
|
||||
private static void verifyRecurType(String recur,
|
||||
int freq, String until, int count, int interval,
|
||||
int[] bysecond, int[] byminute, int[] byhour,
|
||||
int[] byday, int[] bydayNum, int[] bymonthday,
|
||||
int[] byyearday, int[] byweekno, int[] bymonth,
|
||||
int[] bysetpos, int wkst) {
|
||||
EventRecurrence eventRecurrence = new EventRecurrence();
|
||||
eventRecurrence.parse(recur);
|
||||
if (eventRecurrence.freq != freq
|
||||
|| !eq(eventRecurrence.until, until)
|
||||
|| eventRecurrence.count != count
|
||||
|| eventRecurrence.interval != interval
|
||||
|| eventRecurrence.wkst != wkst) {
|
||||
System.out.println("Error... got:");
|
||||
print(eventRecurrence);
|
||||
System.out.println("expected:");
|
||||
System.out.println("{");
|
||||
System.out.println(" freq=" + freq);
|
||||
System.out.println(" until=" + until);
|
||||
System.out.println(" count=" + count);
|
||||
System.out.println(" interval=" + interval);
|
||||
System.out.println(" wkst=" + wkst);
|
||||
System.out.println(" bysecond=" + Arrays.toString(bysecond));
|
||||
System.out.println(" byminute=" + Arrays.toString(byminute));
|
||||
System.out.println(" byhour=" + Arrays.toString(byhour));
|
||||
System.out.println(" byday=" + Arrays.toString(byday));
|
||||
System.out.println(" bydayNum=" + Arrays.toString(bydayNum));
|
||||
System.out.println(" bymonthday=" + Arrays.toString(bymonthday));
|
||||
System.out.println(" byyearday=" + Arrays.toString(byyearday));
|
||||
System.out.println(" byweekno=" + Arrays.toString(byweekno));
|
||||
System.out.println(" bymonth=" + Arrays.toString(bymonth));
|
||||
System.out.println(" bysetpos=" + Arrays.toString(bysetpos));
|
||||
System.out.println("}");
|
||||
throw new RuntimeException("Mismatch in fields");
|
||||
}
|
||||
cmp(eventRecurrence.bysecondCount, eventRecurrence.bysecond, bysecond, "bysecond");
|
||||
cmp(eventRecurrence.byminuteCount, eventRecurrence.byminute, byminute, "byminute");
|
||||
cmp(eventRecurrence.byhourCount, eventRecurrence.byhour, byhour, "byhour");
|
||||
cmp(eventRecurrence.bydayCount, eventRecurrence.byday, byday, "byday");
|
||||
cmp(eventRecurrence.bydayCount, eventRecurrence.bydayNum, bydayNum, "bydayNum");
|
||||
cmp(eventRecurrence.bymonthdayCount, eventRecurrence.bymonthday, bymonthday, "bymonthday");
|
||||
cmp(eventRecurrence.byyeardayCount, eventRecurrence.byyearday, byyearday, "byyearday");
|
||||
cmp(eventRecurrence.byweeknoCount, eventRecurrence.byweekno, byweekno, "byweekno");
|
||||
cmp(eventRecurrence.bymonthCount, eventRecurrence.bymonth, bymonth, "bymonth");
|
||||
cmp(eventRecurrence.bysetposCount, eventRecurrence.bysetpos, bysetpos, "bysetpos");
|
||||
}
|
||||
|
||||
private static void print(EventRecurrence er) {
|
||||
System.out.println("{");
|
||||
System.out.println(" freq=" + er.freq);
|
||||
System.out.println(" until=" + er.until);
|
||||
System.out.println(" count=" + er.count);
|
||||
System.out.println(" interval=" + er.interval);
|
||||
System.out.println(" wkst=" + er.wkst);
|
||||
System.out.println(" bysecond=" + Arrays.toString(er.bysecond));
|
||||
System.out.println(" bysecondCount=" + er.bysecondCount);
|
||||
System.out.println(" byminute=" + Arrays.toString(er.byminute));
|
||||
System.out.println(" byminuteCount=" + er.byminuteCount);
|
||||
System.out.println(" byhour=" + Arrays.toString(er.byhour));
|
||||
System.out.println(" byhourCount=" + er.byhourCount);
|
||||
System.out.println(" byday=" + Arrays.toString(er.byday));
|
||||
System.out.println(" bydayNum=" + Arrays.toString(er.bydayNum));
|
||||
System.out.println(" bydayCount=" + er.bydayCount);
|
||||
System.out.println(" bymonthday=" + Arrays.toString(er.bymonthday));
|
||||
System.out.println(" bymonthdayCount=" + er.bymonthdayCount);
|
||||
System.out.println(" byyearday=" + Arrays.toString(er.byyearday));
|
||||
System.out.println(" byyeardayCount=" + er.byyeardayCount);
|
||||
System.out.println(" byweekno=" + Arrays.toString(er.byweekno));
|
||||
System.out.println(" byweeknoCount=" + er.byweeknoCount);
|
||||
System.out.println(" bymonth=" + Arrays.toString(er.bymonth));
|
||||
System.out.println(" bymonthCount=" + er.bymonthCount);
|
||||
System.out.println(" bysetpos=" + Arrays.toString(er.bysetpos));
|
||||
System.out.println(" bysetposCount=" + er.bysetposCount);
|
||||
System.out.println("}");
|
||||
}
|
||||
|
||||
|
||||
/** A list of valid rules. The parser must accept these. */
|
||||
private static final String[] GOOD_RRULES = {
|
||||
/* extracted wholesale from from RFC 2445 section 4.8.5.4 */
|
||||
"FREQ=DAILY;COUNT=10",
|
||||
"FREQ=DAILY;UNTIL=19971224T000000Z",
|
||||
"FREQ=DAILY;INTERVAL=2",
|
||||
"FREQ=DAILY;INTERVAL=10;COUNT=5",
|
||||
"FREQ=YEARLY;UNTIL=20000131T090000Z;BYMONTH=1;BYDAY=SU,MO,TU,WE,TH,FR,SA",
|
||||
"FREQ=DAILY;UNTIL=20000131T090000Z;BYMONTH=1",
|
||||
"FREQ=WEEKLY;COUNT=10",
|
||||
"FREQ=WEEKLY;UNTIL=19971224T000000Z",
|
||||
"FREQ=WEEKLY;INTERVAL=2;WKST=SU",
|
||||
"FREQ=WEEKLY;UNTIL=19971007T000000Z;WKST=SU;BYDAY=TU,TH",
|
||||
"FREQ=WEEKLY;COUNT=10;WKST=SU;BYDAY=TU,TH",
|
||||
"FREQ=WEEKLY;INTERVAL=2;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR",
|
||||
"FREQ=WEEKLY;INTERVAL=2;COUNT=8;WKST=SU;BYDAY=TU,TH",
|
||||
"FREQ=MONTHLY;COUNT=10;BYDAY=1FR",
|
||||
"FREQ=MONTHLY;UNTIL=19971224T000000Z;BYDAY=1FR",
|
||||
"FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=1SU,-1SU",
|
||||
"FREQ=MONTHLY;COUNT=6;BYDAY=-2MO",
|
||||
"FREQ=MONTHLY;BYMONTHDAY=-3",
|
||||
"FREQ=MONTHLY;COUNT=10;BYMONTHDAY=2,15",
|
||||
"FREQ=MONTHLY;COUNT=10;BYMONTHDAY=1,-1",
|
||||
"FREQ=MONTHLY;INTERVAL=18;COUNT=10;BYMONTHDAY=10,11,12,13,14,15",
|
||||
"FREQ=MONTHLY;INTERVAL=2;BYDAY=TU",
|
||||
"FREQ=YEARLY;COUNT=10;BYMONTH=6,7",
|
||||
"FREQ=YEARLY;INTERVAL=2;COUNT=10;BYMONTH=1,2,3",
|
||||
"FREQ=YEARLY;INTERVAL=3;COUNT=10;BYYEARDAY=1,100,200",
|
||||
"FREQ=YEARLY;BYDAY=20MO",
|
||||
"FREQ=YEARLY;BYWEEKNO=20;BYDAY=MO",
|
||||
"FREQ=YEARLY;BYMONTH=3;BYDAY=TH",
|
||||
"FREQ=YEARLY;BYDAY=TH;BYMONTH=6,7,8",
|
||||
"FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13",
|
||||
"FREQ=MONTHLY;BYDAY=SA;BYMONTHDAY=7,8,9,10,11,12,13",
|
||||
"FREQ=YEARLY;INTERVAL=4;BYMONTH=11;BYDAY=TU;BYMONTHDAY=2,3,4,5,6,7,8",
|
||||
"FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=3",
|
||||
"FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-2",
|
||||
"FREQ=HOURLY;INTERVAL=3;UNTIL=19970902T170000Z",
|
||||
"FREQ=MINUTELY;INTERVAL=15;COUNT=6",
|
||||
"FREQ=MINUTELY;INTERVAL=90;COUNT=4",
|
||||
"FREQ=DAILY;BYHOUR=9,10,11,12,13,14,15,16;BYMINUTE=0,20,40",
|
||||
"FREQ=MINUTELY;INTERVAL=20;BYHOUR=9,10,11,12,13,14,15,16",
|
||||
"FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=MO",
|
||||
"FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=SU",
|
||||
/* a few more */
|
||||
"FREQ=SECONDLY;BYSECOND=0,15,59",
|
||||
"FREQ=MINUTELY;BYMINUTE=0,15,59",
|
||||
"FREQ=HOURLY;BYHOUR=+0,+15,+23",
|
||||
"FREQ=DAILY;X-WHATEVER=blah", // fails on old parser
|
||||
//"freq=daily;wkst=su", // fails on old parser
|
||||
};
|
||||
|
||||
/** The parser must reject these. */
|
||||
private static final String[] BAD_RRULES = {
|
||||
"INTERVAL=4;FREQ=YEARLY", // FREQ must come first
|
||||
"FREQ=MONTHLY;FREQ=MONTHLY", // can't specify twice
|
||||
"FREQ=MONTHLY;COUNT=1;COUNT=1", // can't specify twice
|
||||
"FREQ=SECONDLY;BYSECOND=60", // range
|
||||
"FREQ=MINUTELY;BYMINUTE=-1", // range
|
||||
"FREQ=HOURLY;BYHOUR=24", // range
|
||||
"FREQ=YEARLY;BYMONTHDAY=0", // zero not valid
|
||||
//"FREQ=YEARLY;COUNT=1;UNTIL=12345", // can't have both COUNT and UNTIL
|
||||
//"FREQ=DAILY;UNTIL=19970829T021400e", // invalid date
|
||||
};
|
||||
|
||||
/**
|
||||
* Simple test of good/bad rules.
|
||||
*/
|
||||
@SmallTest
|
||||
public void testBasicParse() {
|
||||
for (String rule : GOOD_RRULES) {
|
||||
EventRecurrence recur = new EventRecurrence();
|
||||
recur.parse(rule);
|
||||
}
|
||||
|
||||
for (String rule : BAD_RRULES) {
|
||||
EventRecurrence recur = new EventRecurrence();
|
||||
boolean didThrow = false;
|
||||
|
||||
try {
|
||||
recur.parse(rule);
|
||||
} catch (InvalidFormatException ife) {
|
||||
didThrow = true;
|
||||
}
|
||||
|
||||
assertTrue("Expected throw on " + rule, didThrow);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.pim;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.pim.ICalendar;
|
||||
import android.pim.RecurrenceSet;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.util.Log;
|
||||
import android.provider.CalendarContract;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* Test some pim.RecurrenceSet functionality.
|
||||
*/
|
||||
public class RecurrenceSetTest extends TestCase {
|
||||
|
||||
// Test a recurrence
|
||||
@SmallTest
|
||||
public void testRecurrenceSet0() throws Exception {
|
||||
String recurrence = "DTSTART;TZID=America/New_York:20080221T070000\n"
|
||||
+ "DTEND;TZID=America/New_York:20080221T190000\n"
|
||||
+ "RRULE:FREQ=DAILY;UNTIL=20080222T000000Z\n"
|
||||
+ "EXDATE:20080222T120000Z";
|
||||
verifyPopulateContentValues(recurrence, "FREQ=DAILY;UNTIL=20080222T000000Z", null,
|
||||
null, "20080222T120000Z", 1203595200000L, "America/New_York", "P43200S", 0);
|
||||
}
|
||||
|
||||
// Test 1 day all-day event
|
||||
@SmallTest
|
||||
public void testRecurrenceSet1() throws Exception {
|
||||
String recurrence = "DTSTART;VALUE=DATE:20090821\nDTEND;VALUE=DATE:20090822\n"
|
||||
+ "RRULE:FREQ=YEARLY;WKST=SU";
|
||||
verifyPopulateContentValues(recurrence, "FREQ=YEARLY;WKST=SU", null,
|
||||
null, null, 1250812800000L, "UTC", "P1D", 1);
|
||||
}
|
||||
|
||||
// Test 2 day all-day event
|
||||
@SmallTest
|
||||
public void testRecurrenceSet2() throws Exception {
|
||||
String recurrence = "DTSTART;VALUE=DATE:20090821\nDTEND;VALUE=DATE:20090823\n"
|
||||
+ "RRULE:FREQ=YEARLY;WKST=SU";
|
||||
verifyPopulateContentValues(recurrence, "FREQ=YEARLY;WKST=SU", null,
|
||||
null, null, 1250812800000L, "UTC", "P2D", 1);
|
||||
}
|
||||
|
||||
// run populateContentValues and verify the results
|
||||
private void verifyPopulateContentValues(String recurrence, String rrule, String rdate,
|
||||
String exrule, String exdate, long dtstart, String tzid, String duration, int allDay)
|
||||
throws ICalendar.FormatException {
|
||||
ICalendar.Component recurrenceComponent =
|
||||
new ICalendar.Component("DUMMY", null /* parent */);
|
||||
ICalendar.parseComponent(recurrenceComponent, recurrence);
|
||||
ContentValues values = new ContentValues();
|
||||
RecurrenceSet.populateContentValues(recurrenceComponent, values);
|
||||
Log.d("KS", "values " + values);
|
||||
|
||||
assertEquals(rrule, values.get(android.provider.CalendarContract.Events.RRULE));
|
||||
assertEquals(rdate, values.get(android.provider.CalendarContract.Events.RDATE));
|
||||
assertEquals(exrule, values.get(android.provider.CalendarContract.Events.EXRULE));
|
||||
assertEquals(exdate, values.get(android.provider.CalendarContract.Events.EXDATE));
|
||||
assertEquals(dtstart, (long) values.getAsLong(CalendarContract.Events.DTSTART));
|
||||
assertEquals(tzid, values.get(android.provider.CalendarContract.Events.EVENT_TIMEZONE));
|
||||
assertEquals(duration, values.get(android.provider.CalendarContract.Events.DURATION));
|
||||
assertEquals(allDay,
|
||||
(int) values.getAsInteger(android.provider.CalendarContract.Events.ALL_DAY));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user