RecurrenceSetpublic class RecurrenceSet extends Object Basic information about a recurrence, following RFC 2445 Section 4.8.5.
Contains the RRULEs, RDATE, EXRULEs, and EXDATE properties. |
Fields Summary |
---|
private static final String | TAG | private static final String | RULE_SEPARATOR | private static final String | FOLDING_SEPARATOR | public EventRecurrence[] | rrules | public long[] | rdates | public EventRecurrence[] | exrules | public long[] | exdates | private static final Pattern | IGNORABLE_ICAL_WHITESPACE_RE | private static final Pattern | FOLD_RE |
Constructors Summary |
---|
public RecurrenceSet(android.content.ContentValues values)Creates a new RecurrenceSet from information stored in the
events table in the CalendarProvider.
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);
| public RecurrenceSet(android.database.Cursor cursor)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.
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)
init(rruleStr, rdateStr, exruleStr, exdateStr);
|
Methods Summary |
---|
public static void | addPropertiesForRuleStr(ICalendar.Component component, java.lang.String propertyName, java.lang.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);
}
| public static void | addPropertyForDateStr(ICalendar.Component component, java.lang.String propertyName, java.lang.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 java.lang.String | computeDuration(android.text.format.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 java.lang.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();
| private static java.lang.String | flattenProperties(ICalendar.Component component, java.lang.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();
| public static java.lang.String | fold(java.lang.String unfoldedIcalContent)fold and unfolds ical content lines as per RFC 2445 section 4.1.
4.1 Content Lines
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).
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.
return FOLD_RE.matcher(unfoldedIcalContent).replaceAll("$0\r\n ");
| private static java.lang.String[] | getRuleStrings(java.lang.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;
| public boolean | hasRecurrence()Returns whether or not a recurrence is defined in this RecurrenceSet.
return (rrules != null || rdates != null);
| private void | init(java.lang.String rruleStr, java.lang.String rdateStr, java.lang.String exruleStr, java.lang.String exdateStr)
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)) {
final List<Long> list = new ArrayList<Long>();
for (String exdate : exdateStr.split(RULE_SEPARATOR)) {
final long[] dates = parseRecurrenceDates(exdate);
for (long date : dates) {
list.add(date);
}
}
exdates = new long[list.size()];
for (int i = 0, n = list.size(); i < n; i++) {
exdates[i] = list.get(i);
}
}
}
| public static long[] | parseRecurrenceDates(java.lang.String recurrence)Parses the provided RDATE or EXDATE string into an array of longs
representing each date/time in the 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'.
try {
time.parse(rawDates[i]);
} catch (TimeFormatException e) {
throw new EventRecurrence.InvalidFormatException(
"TimeFormatException thrown when parsing time " + rawDates[i]
+ " in recurrence " + recurrence);
}
dates[i] = time.toMillis(false /* use isDst */);
time.timezone = tz;
}
return dates;
| public static boolean | populateComponent(android.database.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(android.content.ContentValues values, ICalendar.Component component)
long dtstart = -1;
if (values.containsKey(CalendarContract.Events.DTSTART)) {
dtstart = values.getAsLong(CalendarContract.Events.DTSTART);
}
final String duration = values.getAsString(CalendarContract.Events.DURATION);
final String tzid = values.getAsString(CalendarContract.Events.EVENT_TIMEZONE);
final String rruleStr = values.getAsString(CalendarContract.Events.RRULE);
final String rdateStr = values.getAsString(CalendarContract.Events.RDATE);
final String exruleStr = values.getAsString(CalendarContract.Events.EXRULE);
final String exdateStr = values.getAsString(CalendarContract.Events.EXDATE);
final Integer allDayInteger = values.getAsInteger(CalendarContract.Events.ALL_DAY);
final 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;
| public static boolean | populateContentValues(ICalendar.Component component, android.content.ContentValues values)Populates the database map of values with the appropriate RRULE, RDATE,
EXRULE, and EXDATE values extracted from the parsed iCalendar component.
try {
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;
} catch (TimeFormatException e) {
// Something is wrong with the format of this event
Log.i(TAG,"Failed to parse event: " + component.toString());
return false;
}
| public static java.lang.String | unfold(java.lang.String foldedIcalContent)
return IGNORABLE_ICAL_WHITESPACE_RE.matcher(
foldedIcalContent).replaceAll("");
|
|