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 1706b3293d5af3454a39681cfd659271551354b8a0Michael Chanpackage com.android.calendarcommon2; 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; 25341231a27ba891e90ded672e54817ff011317931Alon Albertimport android.util.TimeFormatException; 260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 27e017a53a1a90ac1c62c1de549d63138bd8237f7dAlon Albertimport java.util.ArrayList; 280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport java.util.List; 290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport java.util.regex.Pattern; 300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden/** 320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Basic information about a recurrence, following RFC 2445 Section 4.8.5. 330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Contains the RRULEs, RDATE, EXRULEs, and EXDATE properties. 340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenpublic class RecurrenceSet { 360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 37e29edf9b71fbec8a7c7f0b523ca105a377632989Andy McFadden private final static String TAG = "RecurrenceSet"; 380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private final static String RULE_SEPARATOR = "\n"; 400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private final static String FOLDING_SEPARATOR = "\n "; 410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // TODO: make these final? 430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public EventRecurrence[] rrules = null; 440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public long[] rdates = null; 450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public EventRecurrence[] exrules = null; 460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public long[] exdates = null; 470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Creates a new RecurrenceSet from information stored in the 500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * events table in the CalendarProvider. 510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param values The values retrieved from the Events table. 520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public RecurrenceSet(ContentValues values) 540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throws EventRecurrence.InvalidFormatException { 550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String rruleStr = values.getAsString(CalendarContract.Events.RRULE); 560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String rdateStr = values.getAsString(CalendarContract.Events.RDATE); 570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String exruleStr = values.getAsString(CalendarContract.Events.EXRULE); 580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String exdateStr = values.getAsString(CalendarContract.Events.EXDATE); 590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden init(rruleStr, rdateStr, exruleStr, exdateStr); 600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Creates a new RecurrenceSet from information stored in a database 640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * {@link Cursor} pointing to the events table in the 650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * CalendarProvider. The cursor must contain the RRULE, RDATE, EXRULE, 660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * and EXDATE columns. 670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * 680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param cursor The cursor containing the RRULE, RDATE, EXRULE, and EXDATE 690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * columns. 700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public RecurrenceSet(Cursor cursor) 720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throws EventRecurrence.InvalidFormatException { 730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int rruleColumn = cursor.getColumnIndex(CalendarContract.Events.RRULE); 740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int rdateColumn = cursor.getColumnIndex(CalendarContract.Events.RDATE); 750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int exruleColumn = cursor.getColumnIndex(CalendarContract.Events.EXRULE); 760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int exdateColumn = cursor.getColumnIndex(CalendarContract.Events.EXDATE); 770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String rruleStr = cursor.getString(rruleColumn); 780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String rdateStr = cursor.getString(rdateColumn); 790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String exruleStr = cursor.getString(exruleColumn); 800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String exdateStr = cursor.getString(exdateColumn); 810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden init(rruleStr, rdateStr, exruleStr, exdateStr); 820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public RecurrenceSet(String rruleStr, String rdateStr, 850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String exruleStr, String exdateStr) 860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throws EventRecurrence.InvalidFormatException { 870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden init(rruleStr, rdateStr, exruleStr, exdateStr); 880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private void init(String rruleStr, String rdateStr, 910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String exruleStr, String exdateStr) 920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throws EventRecurrence.InvalidFormatException { 930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!TextUtils.isEmpty(rruleStr) || !TextUtils.isEmpty(rdateStr)) { 940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!TextUtils.isEmpty(rruleStr)) { 960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String[] rruleStrs = rruleStr.split(RULE_SEPARATOR); 970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden rrules = new EventRecurrence[rruleStrs.length]; 980d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden for (int i = 0; i < rruleStrs.length; ++i) { 990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden EventRecurrence rrule = new EventRecurrence(); 1000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden rrule.parse(rruleStrs[i]); 1010d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden rrules[i] = rrule; 1020d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1030d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1040d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!TextUtils.isEmpty(rdateStr)) { 1060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden rdates = parseRecurrenceDates(rdateStr); 1070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1080d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1090d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!TextUtils.isEmpty(exruleStr)) { 1100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String[] exruleStrs = exruleStr.split(RULE_SEPARATOR); 1110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden exrules = new EventRecurrence[exruleStrs.length]; 1120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden for (int i = 0; i < exruleStrs.length; ++i) { 1130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden EventRecurrence exrule = new EventRecurrence(); 1140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden exrule.parse(exruleStr); 1150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden exrules[i] = exrule; 1160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1170d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!TextUtils.isEmpty(exdateStr)) { 120e017a53a1a90ac1c62c1de549d63138bd8237f7dAlon Albert final List<Long> list = new ArrayList<Long>(); 121e017a53a1a90ac1c62c1de549d63138bd8237f7dAlon Albert for (String exdate : exdateStr.split(RULE_SEPARATOR)) { 122e017a53a1a90ac1c62c1de549d63138bd8237f7dAlon Albert final long[] dates = parseRecurrenceDates(exdate); 123e017a53a1a90ac1c62c1de549d63138bd8237f7dAlon Albert for (long date : dates) { 124e017a53a1a90ac1c62c1de549d63138bd8237f7dAlon Albert list.add(date); 125e017a53a1a90ac1c62c1de549d63138bd8237f7dAlon Albert } 126e017a53a1a90ac1c62c1de549d63138bd8237f7dAlon Albert } 127e017a53a1a90ac1c62c1de549d63138bd8237f7dAlon Albert exdates = new long[list.size()]; 128e017a53a1a90ac1c62c1de549d63138bd8237f7dAlon Albert for (int i = 0, n = list.size(); i < n; i++) { 129e017a53a1a90ac1c62c1de549d63138bd8237f7dAlon Albert exdates[i] = list.get(i); 130e017a53a1a90ac1c62c1de549d63138bd8237f7dAlon Albert } 1310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 1360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Returns whether or not a recurrence is defined in this RecurrenceSet. 1370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @return Whether or not a recurrence is defined in this RecurrenceSet. 1380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 1390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public boolean hasRecurrence() { 1400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return (rrules != null || rdates != null); 1410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 1440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Parses the provided RDATE or EXDATE string into an array of longs 1450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * representing each date/time in the recurrence. 1460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param recurrence The recurrence to be parsed. 1470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @return The list of date/times. 1480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 149341231a27ba891e90ded672e54817ff011317931Alon Albert public static long[] parseRecurrenceDates(String recurrence) 150341231a27ba891e90ded672e54817ff011317931Alon Albert throws EventRecurrence.InvalidFormatException{ 1510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // TODO: use "local" time as the default. will need to handle times 1520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // that end in "z" (UTC time) explicitly at that point. 1530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String tz = Time.TIMEZONE_UTC; 1540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int tzidx = recurrence.indexOf(";"); 1550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (tzidx != -1) { 1560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden tz = recurrence.substring(0, tzidx); 1570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden recurrence = recurrence.substring(tzidx + 1); 1580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden Time time = new Time(tz); 1600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String[] rawDates = recurrence.split(","); 1610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int n = rawDates.length; 1620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden long[] dates = new long[n]; 1630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden for (int i = 0; i<n; ++i) { 1640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // The timezone is updated to UTC if the time string specified 'Z'. 165341231a27ba891e90ded672e54817ff011317931Alon Albert try { 166341231a27ba891e90ded672e54817ff011317931Alon Albert time.parse(rawDates[i]); 167341231a27ba891e90ded672e54817ff011317931Alon Albert } catch (TimeFormatException e) { 168341231a27ba891e90ded672e54817ff011317931Alon Albert throw new EventRecurrence.InvalidFormatException( 169341231a27ba891e90ded672e54817ff011317931Alon Albert "TimeFormatException thrown when parsing time " + rawDates[i] 170341231a27ba891e90ded672e54817ff011317931Alon Albert + " in recurrence " + recurrence); 171341231a27ba891e90ded672e54817ff011317931Alon Albert 172341231a27ba891e90ded672e54817ff011317931Alon Albert } 1730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dates[i] = time.toMillis(false /* use isDst */); 1740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden time.timezone = tz; 1750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return dates; 1770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 1800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Populates the database map of values with the appropriate RRULE, RDATE, 1810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * EXRULE, and EXDATE values extracted from the parsed iCalendar component. 1820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param component The iCalendar component containing the desired 1830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * recurrence specification. 1840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param values The db values that should be updated. 1850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @return true if the component contained the necessary information 1860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * to specify a recurrence. The required fields are DTSTART, 1870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * one of DTEND/DURATION, and one of RRULE/RDATE. Returns false if 1880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * there was an error, including if the date is out of range. 1890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 1900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static boolean populateContentValues(ICalendar.Component component, 1910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ContentValues values) { 192bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson try { 193bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson ICalendar.Property dtstartProperty = 194bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson component.getFirstProperty("DTSTART"); 195bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson String dtstart = dtstartProperty.getValue(); 196bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson ICalendar.Parameter tzidParam = 197bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson dtstartProperty.getFirstParameter("TZID"); 198bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson // NOTE: the timezone may be null, if this is a floating time. 199bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson String tzid = tzidParam == null ? null : tzidParam.value; 200bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson Time start = new Time(tzidParam == null ? Time.TIMEZONE_UTC : tzid); 201bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson boolean inUtc = start.parse(dtstart); 202bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson boolean allDay = start.allDay; 203bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson 204bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson // We force TimeZone to UTC for "all day recurring events" as the server is sending no 205bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson // TimeZone in DTSTART for them 206bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson if (inUtc || allDay) { 207bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson tzid = Time.TIMEZONE_UTC; 208bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson } 209e29edf9b71fbec8a7c7f0b523ca105a377632989Andy McFadden 210bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson String duration = computeDuration(start, component); 211bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson String rrule = flattenProperties(component, "RRULE"); 212bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson String rdate = extractDates(component.getFirstProperty("RDATE")); 213bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson String exrule = flattenProperties(component, "EXRULE"); 214bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson String exdate = extractDates(component.getFirstProperty("EXDATE")); 215bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson 216bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson if ((TextUtils.isEmpty(dtstart))|| 217bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson (TextUtils.isEmpty(duration))|| 218bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson ((TextUtils.isEmpty(rrule))&& 219bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson (TextUtils.isEmpty(rdate)))) { 220bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson if (false) { 221bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson Log.d(TAG, "Recurrence missing DTSTART, DTEND/DURATION, " 222bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson + "or RRULE/RDATE: " 223bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson + component.toString()); 224bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson } 225bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson return false; 226bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson } 2270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 228bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson if (allDay) { 229bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson start.timezone = Time.TIMEZONE_UTC; 230bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson } 231bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson long millis = start.toMillis(false /* use isDst */); 232bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson values.put(CalendarContract.Events.DTSTART, millis); 233bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson if (millis == -1) { 2340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (false) { 235bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson Log.d(TAG, "DTSTART is out of range: " + component.toString()); 2360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return false; 2380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 239bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson 240bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson values.put(CalendarContract.Events.RRULE, rrule); 241bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson values.put(CalendarContract.Events.RDATE, rdate); 242bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson values.put(CalendarContract.Events.EXRULE, exrule); 243bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson values.put(CalendarContract.Events.EXDATE, exdate); 244bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson values.put(CalendarContract.Events.EVENT_TIMEZONE, tzid); 245bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson values.put(CalendarContract.Events.DURATION, duration); 246bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson values.put(CalendarContract.Events.ALL_DAY, allDay ? 1 : 0); 247bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson return true; 248bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson } catch (TimeFormatException e) { 249bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson // Something is wrong with the format of this event 250bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson Log.i(TAG,"Failed to parse event: " + component.toString()); 2510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return false; 2520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // This can be removed when the old CalendarSyncAdapter is removed. 2560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static boolean populateComponent(Cursor cursor, 2570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Component component) { 258e29edf9b71fbec8a7c7f0b523ca105a377632989Andy McFadden 2590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int dtstartColumn = cursor.getColumnIndex(CalendarContract.Events.DTSTART); 2600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int durationColumn = cursor.getColumnIndex(CalendarContract.Events.DURATION); 2610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int tzidColumn = cursor.getColumnIndex(CalendarContract.Events.EVENT_TIMEZONE); 2620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int rruleColumn = cursor.getColumnIndex(CalendarContract.Events.RRULE); 2630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int rdateColumn = cursor.getColumnIndex(CalendarContract.Events.RDATE); 2640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int exruleColumn = cursor.getColumnIndex(CalendarContract.Events.EXRULE); 2650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int exdateColumn = cursor.getColumnIndex(CalendarContract.Events.EXDATE); 2660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int allDayColumn = cursor.getColumnIndex(CalendarContract.Events.ALL_DAY); 2670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden long dtstart = -1; 2700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!cursor.isNull(dtstartColumn)) { 2710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstart = cursor.getLong(dtstartColumn); 2720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String duration = cursor.getString(durationColumn); 2740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String tzid = cursor.getString(tzidColumn); 2750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String rruleStr = cursor.getString(rruleColumn); 2760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String rdateStr = cursor.getString(rdateColumn); 2770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String exruleStr = cursor.getString(exruleColumn); 2780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String exdateStr = cursor.getString(exdateColumn); 2790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden boolean allDay = cursor.getInt(allDayColumn) == 1; 2800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if ((dtstart == -1) || 2820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden (TextUtils.isEmpty(duration))|| 2830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ((TextUtils.isEmpty(rruleStr))&& 2840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden (TextUtils.isEmpty(rdateStr)))) { 2850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // no recurrence. 2860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return false; 2870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Property dtstartProp = new ICalendar.Property("DTSTART"); 2900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden Time dtstartTime = null; 2910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!TextUtils.isEmpty(tzid)) { 2920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!allDay) { 2930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartProp.addParameter(new ICalendar.Parameter("TZID", tzid)); 2940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime = new Time(tzid); 2960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } else { 2970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // use the "floating" timezone 2980d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime = new Time(Time.TIMEZONE_UTC); 2990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 300e29edf9b71fbec8a7c7f0b523ca105a377632989Andy McFadden 3010d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.set(dtstart); 3020d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // make sure the time is printed just as a date, if all day. 3030d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // TODO: android.pim.Time really should take care of this for us. 3040d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (allDay) { 3050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartProp.addParameter(new ICalendar.Parameter("VALUE", "DATE")); 3060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.allDay = true; 3070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.hour = 0; 3080d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.minute = 0; 3090d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.second = 0; 3100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartProp.setValue(dtstartTime.format2445()); 3130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden component.addProperty(dtstartProp); 3140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Property durationProp = new ICalendar.Property("DURATION"); 3150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden durationProp.setValue(duration); 3160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden component.addProperty(durationProp); 3170d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden addPropertiesForRuleStr(component, "RRULE", rruleStr); 3190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden addPropertyForDateStr(component, "RDATE", rdateStr); 3200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden addPropertiesForRuleStr(component, "EXRULE", exruleStr); 3210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden addPropertyForDateStr(component, "EXDATE", exdateStr); 3220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return true; 3230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenpublic static boolean populateComponent(ContentValues values, 3260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Component component) { 3270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden long dtstart = -1; 3280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (values.containsKey(CalendarContract.Events.DTSTART)) { 3290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstart = values.getAsLong(CalendarContract.Events.DTSTART); 3300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3311546ea7c0d8791a690af9326904f1472321c95dfAlon Albert final String duration = values.getAsString(CalendarContract.Events.DURATION); 3321546ea7c0d8791a690af9326904f1472321c95dfAlon Albert final String tzid = values.getAsString(CalendarContract.Events.EVENT_TIMEZONE); 3331546ea7c0d8791a690af9326904f1472321c95dfAlon Albert final String rruleStr = values.getAsString(CalendarContract.Events.RRULE); 3341546ea7c0d8791a690af9326904f1472321c95dfAlon Albert final String rdateStr = values.getAsString(CalendarContract.Events.RDATE); 3351546ea7c0d8791a690af9326904f1472321c95dfAlon Albert final String exruleStr = values.getAsString(CalendarContract.Events.EXRULE); 3361546ea7c0d8791a690af9326904f1472321c95dfAlon Albert final String exdateStr = values.getAsString(CalendarContract.Events.EXDATE); 3371546ea7c0d8791a690af9326904f1472321c95dfAlon Albert final Integer allDayInteger = values.getAsInteger(CalendarContract.Events.ALL_DAY); 3381546ea7c0d8791a690af9326904f1472321c95dfAlon Albert final boolean allDay = (null != allDayInteger) ? (allDayInteger == 1) : false; 3390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if ((dtstart == -1) || 3410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden (TextUtils.isEmpty(duration))|| 3420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ((TextUtils.isEmpty(rruleStr))&& 3430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden (TextUtils.isEmpty(rdateStr)))) { 3440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // no recurrence. 3450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return false; 3460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Property dtstartProp = new ICalendar.Property("DTSTART"); 3490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden Time dtstartTime = null; 3500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!TextUtils.isEmpty(tzid)) { 3510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!allDay) { 3520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartProp.addParameter(new ICalendar.Parameter("TZID", tzid)); 3530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime = new Time(tzid); 3550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } else { 3560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // use the "floating" timezone 3570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime = new Time(Time.TIMEZONE_UTC); 3580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.set(dtstart); 3610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // make sure the time is printed just as a date, if all day. 3620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // TODO: android.pim.Time really should take care of this for us. 3630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (allDay) { 3640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartProp.addParameter(new ICalendar.Parameter("VALUE", "DATE")); 3650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.allDay = true; 3660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.hour = 0; 3670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.minute = 0; 3680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartTime.second = 0; 3690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtstartProp.setValue(dtstartTime.format2445()); 3720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden component.addProperty(dtstartProp); 3730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Property durationProp = new ICalendar.Property("DURATION"); 3740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden durationProp.setValue(duration); 3750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden component.addProperty(durationProp); 3760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden addPropertiesForRuleStr(component, "RRULE", rruleStr); 3780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden addPropertyForDateStr(component, "RDATE", rdateStr); 3790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden addPropertiesForRuleStr(component, "EXRULE", exruleStr); 3800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden addPropertyForDateStr(component, "EXDATE", exdateStr); 3810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return true; 3820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3841546ea7c0d8791a690af9326904f1472321c95dfAlon Albert public static void addPropertiesForRuleStr(ICalendar.Component component, 3850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String propertyName, 3860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String ruleStr) { 3870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (TextUtils.isEmpty(ruleStr)) { 3880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return; 3890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String[] rrules = getRuleStrings(ruleStr); 3910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden for (String rrule : rrules) { 3920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Property prop = new ICalendar.Property(propertyName); 3930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden prop.setValue(rrule); 3940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden component.addProperty(prop); 3950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3980d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private static String[] getRuleStrings(String ruleStr) { 3990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (null == ruleStr) { 4000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return new String[0]; 4010d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4020d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String unfoldedRuleStr = unfold(ruleStr); 4030d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String[] split = unfoldedRuleStr.split(RULE_SEPARATOR); 4040d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int count = split.length; 4050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden for (int n = 0; n < count; n++) { 4060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden split[n] = fold(split[n]); 4070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4080d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return split; 4090d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private static final Pattern IGNORABLE_ICAL_WHITESPACE_RE = 4130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden Pattern.compile("(?:\\r\\n?|\\n)[ \t]"); 4140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private static final Pattern FOLD_RE = Pattern.compile(".{75}"); 4160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4170d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 4180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * fold and unfolds ical content lines as per RFC 2445 section 4.1. 4190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * 4200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * <h3>4.1 Content Lines</h3> 4210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * 4220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * <p>The iCalendar object is organized into individual lines of text, called 4230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * content lines. Content lines are delimited by a line break, which is a CRLF 4240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * sequence (US-ASCII decimal 13, followed by US-ASCII decimal 10). 4250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * 4260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * <p>Lines of text SHOULD NOT be longer than 75 octets, excluding the line 4270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * break. Long content lines SHOULD be split into a multiple line 4280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * representations using a line "folding" technique. That is, a long line can 4290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * be split between any two characters by inserting a CRLF immediately 4300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * followed by a single linear white space character (i.e., SPACE, US-ASCII 4310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * decimal 32 or HTAB, US-ASCII decimal 9). Any sequence of CRLF followed 4320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * immediately by a single linear white space character is ignored (i.e., 4330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * removed) when processing the content type. 4340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 4350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static String fold(String unfoldedIcalContent) { 4360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return FOLD_RE.matcher(unfoldedIcalContent).replaceAll("$0\r\n "); 4370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static String unfold(String foldedIcalContent) { 4400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return IGNORABLE_ICAL_WHITESPACE_RE.matcher( 4410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden foldedIcalContent).replaceAll(""); 4420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4441546ea7c0d8791a690af9326904f1472321c95dfAlon Albert public static void addPropertyForDateStr(ICalendar.Component component, 4450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String propertyName, 4460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String dateStr) { 4470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (TextUtils.isEmpty(dateStr)) { 4480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return; 4490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Property prop = new ICalendar.Property(propertyName); 4520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String tz = null; 4530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int tzidx = dateStr.indexOf(";"); 4540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (tzidx != -1) { 4550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden tz = dateStr.substring(0, tzidx); 4560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dateStr = dateStr.substring(tzidx + 1); 4570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!TextUtils.isEmpty(tz)) { 4590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden prop.addParameter(new ICalendar.Parameter("TZID", tz)); 4600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden prop.setValue(dateStr); 4620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden component.addProperty(prop); 4630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 464e29edf9b71fbec8a7c7f0b523ca105a377632989Andy McFadden 4650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private static String computeDuration(Time start, 4660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Component component) { 4670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // see if a duration is defined 4680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Property durationProperty = 4690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden component.getFirstProperty("DURATION"); 4700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (durationProperty != null) { 4710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // just return the duration 4720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return durationProperty.getValue(); 4730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // must compute a duration from the DTEND 4760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Property dtendProperty = 4770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden component.getFirstProperty("DTEND"); 4780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (dtendProperty == null) { 4790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // no DURATION, no DTEND: 0 second duration 4800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return "+P0S"; 4810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Parameter endTzidParameter = 4830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden dtendProperty.getFirstParameter("TZID"); 4840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String endTzid = (endTzidParameter == null) 4850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ? start.timezone : endTzidParameter.value; 4860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden Time end = new Time(endTzid); 4880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden end.parse(dtendProperty.getValue()); 4890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden long durationMillis = end.toMillis(false /* use isDst */) 4900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden - start.toMillis(false /* use isDst */); 4910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden long durationSeconds = (durationMillis / 1000); 4920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (start.allDay && (durationSeconds % 86400) == 0) { 4930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return "P" + (durationSeconds / 86400) + "D"; // Server wants this instead of P86400S 4940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } else { 4950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return "P" + durationSeconds + "S"; 4960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4980d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private static String flattenProperties(ICalendar.Component component, 5000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String name) { 5010d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden List<ICalendar.Property> properties = component.getProperties(name); 5020d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (properties == null || properties.isEmpty()) { 5030d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return null; 5040d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 5060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (properties.size() == 1) { 5070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return properties.get(0).getValue(); 5080d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5090d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 5100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden StringBuilder sb = new StringBuilder(); 5110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 5120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden boolean first = true; 5130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden for (ICalendar.Property property : component.getProperties(name)) { 5140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (first) { 5150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden first = false; 5160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } else { 5170d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // TODO: use commas. our RECUR parsing should handle that 5180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // anyway. 5190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden sb.append(RULE_SEPARATOR); 5200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden sb.append(property.getValue()); 5220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return sb.toString(); 5240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 5260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private static String extractDates(ICalendar.Property recurrence) { 5270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (recurrence == null) { 5280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return null; 5290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ICalendar.Parameter tzidParam = 5310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden recurrence.getFirstParameter("TZID"); 5320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (tzidParam != null) { 5330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return tzidParam.value + ";" + recurrence.getValue(); 5340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return recurrence.getValue(); 5360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden} 538