RecurrenceSet.java revision 0d3524562e330e74f150a17c4dc4dd66a0faae46
10d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden/* 20d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Copyright (C) 2007 The Android Open Source Project 30d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * 40d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Licensed under the Apache License, Version 2.0 (the "License"); 50d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * you may not use this file except in compliance with the License. 60d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * You may obtain a copy of the License at 70d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * 80d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * http://www.apache.org/licenses/LICENSE-2.0 90d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * 100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Unless required by applicable law or agreed to in writing, software 110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * distributed under the License is distributed on an "AS IS" BASIS, 120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * See the License for the specific language governing permissions and 140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * limitations under the License. 150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 170d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenpackage com.android.calendarcommon; 180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport android.content.ContentValues; 200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport android.database.Cursor; 210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport android.provider.CalendarContract; 220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport android.text.TextUtils; 230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport android.text.format.Time; 240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport android.util.Log; 250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport java.util.List; 270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport java.util.regex.Pattern; 280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden/** 300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Basic information about a recurrence, following RFC 2445 Section 4.8.5. 310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Contains the RRULEs, RDATE, EXRULEs, and EXDATE properties. 320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenpublic class RecurrenceSet { 340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private final static String TAG = "CalendarProvider"; 360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private final static String RULE_SEPARATOR = "\n"; 380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private final static String FOLDING_SEPARATOR = "\n "; 390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // TODO: make these final? 410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public EventRecurrence[] rrules = null; 420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public long[] rdates = null; 430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public EventRecurrence[] exrules = null; 440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public long[] exdates = null; 450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Creates a new RecurrenceSet from information stored in the 480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * events table in the CalendarProvider. 490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param values The values retrieved from the Events table. 500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public RecurrenceSet(ContentValues values) 520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throws EventRecurrence.InvalidFormatException { 530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String rruleStr = values.getAsString(CalendarContract.Events.RRULE); 540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String rdateStr = values.getAsString(CalendarContract.Events.RDATE); 550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String exruleStr = values.getAsString(CalendarContract.Events.EXRULE); 560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String exdateStr = values.getAsString(CalendarContract.Events.EXDATE); 570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden init(rruleStr, rdateStr, exruleStr, exdateStr); 580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Creates a new RecurrenceSet from information stored in a database 620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * {@link Cursor} pointing to the events table in the 630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * CalendarProvider. The cursor must contain the RRULE, RDATE, EXRULE, 640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * and EXDATE columns. 650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * 660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param cursor The cursor containing the RRULE, RDATE, EXRULE, and EXDATE 670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * columns. 680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public RecurrenceSet(Cursor cursor) 700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throws EventRecurrence.InvalidFormatException { 710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int rruleColumn = cursor.getColumnIndex(CalendarContract.Events.RRULE); 720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int rdateColumn = cursor.getColumnIndex(CalendarContract.Events.RDATE); 730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int exruleColumn = cursor.getColumnIndex(CalendarContract.Events.EXRULE); 740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int exdateColumn = cursor.getColumnIndex(CalendarContract.Events.EXDATE); 750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String rruleStr = cursor.getString(rruleColumn); 760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String rdateStr = cursor.getString(rdateColumn); 770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String exruleStr = cursor.getString(exruleColumn); 780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String exdateStr = cursor.getString(exdateColumn); 790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden init(rruleStr, rdateStr, exruleStr, exdateStr); 800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public RecurrenceSet(String rruleStr, String rdateStr, 830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String exruleStr, String exdateStr) 840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throws EventRecurrence.InvalidFormatException { 850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden init(rruleStr, rdateStr, exruleStr, exdateStr); 860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private void init(String rruleStr, String rdateStr, 890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String exruleStr, String exdateStr) 900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throws EventRecurrence.InvalidFormatException { 910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!TextUtils.isEmpty(rruleStr) || !TextUtils.isEmpty(rdateStr)) { 920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!TextUtils.isEmpty(rruleStr)) { 940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String[] rruleStrs = rruleStr.split(RULE_SEPARATOR); 950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden rrules = new EventRecurrence[rruleStrs.length]; 960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden for (int i = 0; i < rruleStrs.length; ++i) { 970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden EventRecurrence rrule = new EventRecurrence(); 980d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden rrule.parse(rruleStrs[i]); 990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden rrules[i] = rrule; 1000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1010d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1020d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1030d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!TextUtils.isEmpty(rdateStr)) { 1040d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden rdates = parseRecurrenceDates(rdateStr); 1050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!TextUtils.isEmpty(exruleStr)) { 1080d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String[] exruleStrs = exruleStr.split(RULE_SEPARATOR); 1090d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden exrules = new EventRecurrence[exruleStrs.length]; 1100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden for (int i = 0; i < exruleStrs.length; ++i) { 1110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden EventRecurrence exrule = new EventRecurrence(); 1120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden exrule.parse(exruleStr); 1130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden exrules[i] = exrule; 1140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1170d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!TextUtils.isEmpty(exdateStr)) { 1180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden exdates = parseRecurrenceDates(exdateStr); 1190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 1240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Returns whether or not a recurrence is defined in this RecurrenceSet. 1250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @return Whether or not a recurrence is defined in this RecurrenceSet. 1260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 1270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public boolean hasRecurrence() { 1280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return (rrules != null || rdates != null); 1290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 1320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Parses the provided RDATE or EXDATE string into an array of longs 1330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * representing each date/time in the recurrence. 1340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param recurrence The recurrence to be parsed. 1350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @return The list of date/times. 1360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 1370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static long[] parseRecurrenceDates(String recurrence) { 1380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // TODO: use "local" time as the default. will need to handle times 1390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // that end in "z" (UTC time) explicitly at that point. 1400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String tz = Time.TIMEZONE_UTC; 1410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int tzidx = recurrence.indexOf(";"); 1420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (tzidx != -1) { 1430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden tz = recurrence.substring(0, tzidx); 1440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden recurrence = recurrence.substring(tzidx + 1); 1450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden Time time = new Time(tz); 1470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String[] rawDates = recurrence.split(","); 1480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int n = rawDates.length; 1490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden long[] dates = new long[n]; 1500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden for (int i = 0; i<n; ++i) { 1510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // The timezone is updated to UTC if the time string specified 'Z'. 1520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden time.parse(rawDates[i]); 1530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dates[i] = time.toMillis(false /* use isDst */); 1540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden time.timezone = tz; 1550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return dates; 1570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 1600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Populates the database map of values with the appropriate RRULE, RDATE, 1610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * EXRULE, and EXDATE values extracted from the parsed iCalendar component. 1620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param component The iCalendar component containing the desired 1630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * recurrence specification. 1640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param values The db values that should be updated. 1650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @return true if the component contained the necessary information 1660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * to specify a recurrence. The required fields are DTSTART, 1670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * one of DTEND/DURATION, and one of RRULE/RDATE. Returns false if 1680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * there was an error, including if the date is out of range. 1690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 1700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static boolean populateContentValues(ICalendar.Component component, 1710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ContentValues values) { 1720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Property dtstartProperty = 1730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden component.getFirstProperty("DTSTART"); 1740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String dtstart = dtstartProperty.getValue(); 1750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Parameter tzidParam = 1760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartProperty.getFirstParameter("TZID"); 1770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // NOTE: the timezone may be null, if this is a floating time. 1780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String tzid = tzidParam == null ? null : tzidParam.value; 1790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden Time start = new Time(tzidParam == null ? Time.TIMEZONE_UTC : tzid); 1800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden boolean inUtc = start.parse(dtstart); 1810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden boolean allDay = start.allDay; 1820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // We force TimeZone to UTC for "all day recurring events" as the server is sending no 1840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // TimeZone in DTSTART for them 1850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (inUtc || allDay) { 1860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden tzid = Time.TIMEZONE_UTC; 1870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String duration = computeDuration(start, component); 1900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String rrule = flattenProperties(component, "RRULE"); 1910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String rdate = extractDates(component.getFirstProperty("RDATE")); 1920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String exrule = flattenProperties(component, "EXRULE"); 1930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String exdate = extractDates(component.getFirstProperty("EXDATE")); 1940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if ((TextUtils.isEmpty(dtstart))|| 1960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden (TextUtils.isEmpty(duration))|| 1970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ((TextUtils.isEmpty(rrule))&& 1980d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden (TextUtils.isEmpty(rdate)))) { 1990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (false) { 2000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden Log.d(TAG, "Recurrence missing DTSTART, DTEND/DURATION, " 2010d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden + "or RRULE/RDATE: " 2020d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden + component.toString()); 2030d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2040d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return false; 2050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (allDay) { 2080d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden start.timezone = Time.TIMEZONE_UTC; 2090d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden long millis = start.toMillis(false /* use isDst */); 2110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden values.put(CalendarContract.Events.DTSTART, millis); 2120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (millis == -1) { 2130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (false) { 2140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden Log.d(TAG, "DTSTART is out of range: " + component.toString()); 2150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return false; 2170d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden values.put(CalendarContract.Events.RRULE, rrule); 2200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden values.put(CalendarContract.Events.RDATE, rdate); 2210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden values.put(CalendarContract.Events.EXRULE, exrule); 2220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden values.put(CalendarContract.Events.EXDATE, exdate); 2230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden values.put(CalendarContract.Events.EVENT_TIMEZONE, tzid); 2240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden values.put(CalendarContract.Events.DURATION, duration); 2250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden values.put(CalendarContract.Events.ALL_DAY, allDay ? 1 : 0); 2260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return true; 2270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // This can be removed when the old CalendarSyncAdapter is removed. 2300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static boolean populateComponent(Cursor cursor, 2310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Component component) { 2320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int dtstartColumn = cursor.getColumnIndex(CalendarContract.Events.DTSTART); 2340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int durationColumn = cursor.getColumnIndex(CalendarContract.Events.DURATION); 2350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int tzidColumn = cursor.getColumnIndex(CalendarContract.Events.EVENT_TIMEZONE); 2360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int rruleColumn = cursor.getColumnIndex(CalendarContract.Events.RRULE); 2370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int rdateColumn = cursor.getColumnIndex(CalendarContract.Events.RDATE); 2380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int exruleColumn = cursor.getColumnIndex(CalendarContract.Events.EXRULE); 2390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int exdateColumn = cursor.getColumnIndex(CalendarContract.Events.EXDATE); 2400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int allDayColumn = cursor.getColumnIndex(CalendarContract.Events.ALL_DAY); 2410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden long dtstart = -1; 2440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!cursor.isNull(dtstartColumn)) { 2450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstart = cursor.getLong(dtstartColumn); 2460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String duration = cursor.getString(durationColumn); 2480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String tzid = cursor.getString(tzidColumn); 2490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String rruleStr = cursor.getString(rruleColumn); 2500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String rdateStr = cursor.getString(rdateColumn); 2510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String exruleStr = cursor.getString(exruleColumn); 2520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String exdateStr = cursor.getString(exdateColumn); 2530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden boolean allDay = cursor.getInt(allDayColumn) == 1; 2540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if ((dtstart == -1) || 2560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden (TextUtils.isEmpty(duration))|| 2570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ((TextUtils.isEmpty(rruleStr))&& 2580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden (TextUtils.isEmpty(rdateStr)))) { 2590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // no recurrence. 2600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return false; 2610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Property dtstartProp = new ICalendar.Property("DTSTART"); 2640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden Time dtstartTime = null; 2650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!TextUtils.isEmpty(tzid)) { 2660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!allDay) { 2670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartProp.addParameter(new ICalendar.Parameter("TZID", tzid)); 2680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime = new Time(tzid); 2700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } else { 2710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // use the "floating" timezone 2720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime = new Time(Time.TIMEZONE_UTC); 2730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.set(dtstart); 2760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // make sure the time is printed just as a date, if all day. 2770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // TODO: android.pim.Time really should take care of this for us. 2780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (allDay) { 2790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartProp.addParameter(new ICalendar.Parameter("VALUE", "DATE")); 2800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.allDay = true; 2810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.hour = 0; 2820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.minute = 0; 2830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.second = 0; 2840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartProp.setValue(dtstartTime.format2445()); 2870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden component.addProperty(dtstartProp); 2880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Property durationProp = new ICalendar.Property("DURATION"); 2890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden durationProp.setValue(duration); 2900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden component.addProperty(durationProp); 2910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden addPropertiesForRuleStr(component, "RRULE", rruleStr); 2930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden addPropertyForDateStr(component, "RDATE", rdateStr); 2940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden addPropertiesForRuleStr(component, "EXRULE", exruleStr); 2950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden addPropertyForDateStr(component, "EXDATE", exdateStr); 2960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return true; 2970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2980d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenpublic static boolean populateComponent(ContentValues values, 3000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Component component) { 3010d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden long dtstart = -1; 3020d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (values.containsKey(CalendarContract.Events.DTSTART)) { 3030d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstart = values.getAsLong(CalendarContract.Events.DTSTART); 3040d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String duration = values.getAsString(CalendarContract.Events.DURATION); 3060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String tzid = values.getAsString(CalendarContract.Events.EVENT_TIMEZONE); 3070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String rruleStr = values.getAsString(CalendarContract.Events.RRULE); 3080d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String rdateStr = values.getAsString(CalendarContract.Events.RDATE); 3090d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String exruleStr = values.getAsString(CalendarContract.Events.EXRULE); 3100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String exdateStr = values.getAsString(CalendarContract.Events.EXDATE); 3110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden Integer allDayInteger = values.getAsInteger(CalendarContract.Events.ALL_DAY); 3120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden boolean allDay = (null != allDayInteger) ? (allDayInteger == 1) : false; 3130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if ((dtstart == -1) || 3150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden (TextUtils.isEmpty(duration))|| 3160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ((TextUtils.isEmpty(rruleStr))&& 3170d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden (TextUtils.isEmpty(rdateStr)))) { 3180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // no recurrence. 3190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return false; 3200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Property dtstartProp = new ICalendar.Property("DTSTART"); 3230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden Time dtstartTime = null; 3240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!TextUtils.isEmpty(tzid)) { 3250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!allDay) { 3260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartProp.addParameter(new ICalendar.Parameter("TZID", tzid)); 3270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime = new Time(tzid); 3290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } else { 3300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // use the "floating" timezone 3310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime = new Time(Time.TIMEZONE_UTC); 3320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.set(dtstart); 3350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // make sure the time is printed just as a date, if all day. 3360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // TODO: android.pim.Time really should take care of this for us. 3370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (allDay) { 3380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartProp.addParameter(new ICalendar.Parameter("VALUE", "DATE")); 3390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.allDay = true; 3400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.hour = 0; 3410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.minute = 0; 3420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.second = 0; 3430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartProp.setValue(dtstartTime.format2445()); 3460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden component.addProperty(dtstartProp); 3470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Property durationProp = new ICalendar.Property("DURATION"); 3480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden durationProp.setValue(duration); 3490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden component.addProperty(durationProp); 3500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden addPropertiesForRuleStr(component, "RRULE", rruleStr); 3520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden addPropertyForDateStr(component, "RDATE", rdateStr); 3530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden addPropertiesForRuleStr(component, "EXRULE", exruleStr); 3540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden addPropertyForDateStr(component, "EXDATE", exdateStr); 3550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return true; 3560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private static void addPropertiesForRuleStr(ICalendar.Component component, 3590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String propertyName, 3600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String ruleStr) { 3610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (TextUtils.isEmpty(ruleStr)) { 3620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return; 3630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String[] rrules = getRuleStrings(ruleStr); 3650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden for (String rrule : rrules) { 3660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Property prop = new ICalendar.Property(propertyName); 3670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden prop.setValue(rrule); 3680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden component.addProperty(prop); 3690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private static String[] getRuleStrings(String ruleStr) { 3730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (null == ruleStr) { 3740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return new String[0]; 3750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String unfoldedRuleStr = unfold(ruleStr); 3770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String[] split = unfoldedRuleStr.split(RULE_SEPARATOR); 3780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int count = split.length; 3790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden for (int n = 0; n < count; n++) { 3800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden split[n] = fold(split[n]); 3810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return split; 3830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private static final Pattern IGNORABLE_ICAL_WHITESPACE_RE = 3870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden Pattern.compile("(?:\\r\\n?|\\n)[ \t]"); 3880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private static final Pattern FOLD_RE = Pattern.compile(".{75}"); 3900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 3920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * fold and unfolds ical content lines as per RFC 2445 section 4.1. 3930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * 3940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * <h3>4.1 Content Lines</h3> 3950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * 3960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * <p>The iCalendar object is organized into individual lines of text, called 3970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * content lines. Content lines are delimited by a line break, which is a CRLF 3980d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * sequence (US-ASCII decimal 13, followed by US-ASCII decimal 10). 3990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * 4000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * <p>Lines of text SHOULD NOT be longer than 75 octets, excluding the line 4010d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * break. Long content lines SHOULD be split into a multiple line 4020d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * representations using a line "folding" technique. That is, a long line can 4030d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * be split between any two characters by inserting a CRLF immediately 4040d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * followed by a single linear white space character (i.e., SPACE, US-ASCII 4050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * decimal 32 or HTAB, US-ASCII decimal 9). Any sequence of CRLF followed 4060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * immediately by a single linear white space character is ignored (i.e., 4070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * removed) when processing the content type. 4080d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 4090d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static String fold(String unfoldedIcalContent) { 4100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return FOLD_RE.matcher(unfoldedIcalContent).replaceAll("$0\r\n "); 4110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static String unfold(String foldedIcalContent) { 4140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return IGNORABLE_ICAL_WHITESPACE_RE.matcher( 4150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden foldedIcalContent).replaceAll(""); 4160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4170d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private static void addPropertyForDateStr(ICalendar.Component component, 4190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String propertyName, 4200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String dateStr) { 4210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (TextUtils.isEmpty(dateStr)) { 4220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return; 4230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Property prop = new ICalendar.Property(propertyName); 4260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String tz = null; 4270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int tzidx = dateStr.indexOf(";"); 4280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (tzidx != -1) { 4290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden tz = dateStr.substring(0, tzidx); 4300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dateStr = dateStr.substring(tzidx + 1); 4310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!TextUtils.isEmpty(tz)) { 4330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden prop.addParameter(new ICalendar.Parameter("TZID", tz)); 4340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden prop.setValue(dateStr); 4360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden component.addProperty(prop); 4370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private static String computeDuration(Time start, 4400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Component component) { 4410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // see if a duration is defined 4420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Property durationProperty = 4430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden component.getFirstProperty("DURATION"); 4440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (durationProperty != null) { 4450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // just return the duration 4460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return durationProperty.getValue(); 4470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // must compute a duration from the DTEND 4500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Property dtendProperty = 4510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden component.getFirstProperty("DTEND"); 4520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (dtendProperty == null) { 4530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // no DURATION, no DTEND: 0 second duration 4540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return "+P0S"; 4550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Parameter endTzidParameter = 4570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtendProperty.getFirstParameter("TZID"); 4580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String endTzid = (endTzidParameter == null) 4590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ? start.timezone : endTzidParameter.value; 4600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden Time end = new Time(endTzid); 4620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden end.parse(dtendProperty.getValue()); 4630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden long durationMillis = end.toMillis(false /* use isDst */) 4640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden - start.toMillis(false /* use isDst */); 4650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden long durationSeconds = (durationMillis / 1000); 4660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (start.allDay && (durationSeconds % 86400) == 0) { 4670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return "P" + (durationSeconds / 86400) + "D"; // Server wants this instead of P86400S 4680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } else { 4690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return "P" + durationSeconds + "S"; 4700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private static String flattenProperties(ICalendar.Component component, 4740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String name) { 4750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden List<ICalendar.Property> properties = component.getProperties(name); 4760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (properties == null || properties.isEmpty()) { 4770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return null; 4780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (properties.size() == 1) { 4810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return properties.get(0).getValue(); 4820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden StringBuilder sb = new StringBuilder(); 4850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden boolean first = true; 4870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden for (ICalendar.Property property : component.getProperties(name)) { 4880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (first) { 4890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden first = false; 4900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } else { 4910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // TODO: use commas. our RECUR parsing should handle that 4920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // anyway. 4930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden sb.append(RULE_SEPARATOR); 4940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden sb.append(property.getValue()); 4960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return sb.toString(); 4980d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 5000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private static String extractDates(ICalendar.Property recurrence) { 5010d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (recurrence == null) { 5020d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return null; 5030d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5040d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Parameter tzidParam = 5050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden recurrence.getFirstParameter("TZID"); 5060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (tzidParam != null) { 5070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return tzidParam.value + ";" + recurrence.getValue(); 5080d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5090d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return recurrence.getValue(); 5100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden} 512