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)) {
943f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka            rrules = parseMultiLineRecurrenceRules(rruleStr);
953f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka            rdates = parseMultiLineRecurrenceDates(rdateStr);
963f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka            exrules = parseMultiLineRecurrenceRules(exruleStr);
973f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka            exdates = parseMultiLineRecurrenceDates(exdateStr);
983f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka        }
993f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka    }
1000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
1013f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka    private EventRecurrence[] parseMultiLineRecurrenceRules(String ruleStr) {
1023f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka        if (TextUtils.isEmpty(ruleStr)) {
1033f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka            return null;
1043f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka        }
1053f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka        String[] ruleStrs = ruleStr.split(RULE_SEPARATOR);
1063f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka        final EventRecurrence[] rules = new EventRecurrence[ruleStrs.length];
1073f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka        for (int i = 0; i < ruleStrs.length; ++i) {
1083f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka            EventRecurrence rule = new EventRecurrence();
1093f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka            rule.parse(ruleStrs[i]);
1103f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka            rules[i] = rule;
1113f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka        }
1123f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka        return rules;
1133f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka    }
1140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
1153f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka    private long[] parseMultiLineRecurrenceDates(String dateStr) {
1163f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka        if (TextUtils.isEmpty(dateStr)) {
1173f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka            return null;
1183f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka        }
1193f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka        final List<Long> list = new ArrayList<>();
1203f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka        for (String date : dateStr.split(RULE_SEPARATOR)) {
1213f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka            final long[] parsedDates = parseRecurrenceDates(date);
1223f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka            for (long parsedDate : parsedDates) {
1233f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka                list.add(parsedDate);
1240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            }
1250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
1263f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka        final long[] result = new long[list.size()];
1273f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka        for (int i = 0, n = list.size(); i < n; i++) {
1283f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka            result[i] = list.get(i);
1293f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka        }
1303f47856377406ce14cc43492808aa5f84cdf52e9Sudheer Shanka        return result;
1310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
1320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
1330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /**
1340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * Returns whether or not a recurrence is defined in this RecurrenceSet.
1350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * @return Whether or not a recurrence is defined in this RecurrenceSet.
1360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     */
1370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public boolean hasRecurrence() {
1380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        return (rrules != null || rdates != null);
1390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
1400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
1410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /**
1420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * Parses the provided RDATE or EXDATE string into an array of longs
1430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * representing each date/time in the recurrence.
1440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * @param recurrence The recurrence to be parsed.
1450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * @return The list of date/times.
1460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     */
147341231a27ba891e90ded672e54817ff011317931Alon Albert    public static long[] parseRecurrenceDates(String recurrence)
148341231a27ba891e90ded672e54817ff011317931Alon Albert            throws EventRecurrence.InvalidFormatException{
1490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        // TODO: use "local" time as the default.  will need to handle times
1500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        // that end in "z" (UTC time) explicitly at that point.
1510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        String tz = Time.TIMEZONE_UTC;
1520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        int tzidx = recurrence.indexOf(";");
1530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (tzidx != -1) {
1540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            tz = recurrence.substring(0, tzidx);
1550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            recurrence = recurrence.substring(tzidx + 1);
1560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
1570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        Time time = new Time(tz);
1580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        String[] rawDates = recurrence.split(",");
1590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        int n = rawDates.length;
1600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        long[] dates = new long[n];
1610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        for (int i = 0; i<n; ++i) {
1620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            // The timezone is updated to UTC if the time string specified 'Z'.
163341231a27ba891e90ded672e54817ff011317931Alon Albert            try {
164341231a27ba891e90ded672e54817ff011317931Alon Albert                time.parse(rawDates[i]);
165341231a27ba891e90ded672e54817ff011317931Alon Albert            } catch (TimeFormatException e) {
166341231a27ba891e90ded672e54817ff011317931Alon Albert                throw new EventRecurrence.InvalidFormatException(
167341231a27ba891e90ded672e54817ff011317931Alon Albert                        "TimeFormatException thrown when parsing time " + rawDates[i]
168341231a27ba891e90ded672e54817ff011317931Alon Albert                                + " in recurrence " + recurrence);
169341231a27ba891e90ded672e54817ff011317931Alon Albert
170341231a27ba891e90ded672e54817ff011317931Alon Albert            }
1710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            dates[i] = time.toMillis(false /* use isDst */);
1720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            time.timezone = tz;
1730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
1740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        return dates;
1750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
1760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
1770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /**
1780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * Populates the database map of values with the appropriate RRULE, RDATE,
1790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * EXRULE, and EXDATE values extracted from the parsed iCalendar component.
1800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * @param component The iCalendar component containing the desired
1810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * recurrence specification.
1820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * @param values The db values that should be updated.
1830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * @return true if the component contained the necessary information
1840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * to specify a recurrence.  The required fields are DTSTART,
1850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * one of DTEND/DURATION, and one of RRULE/RDATE.  Returns false if
1860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     * there was an error, including if the date is out of range.
1870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden     */
1880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public static boolean populateContentValues(ICalendar.Component component,
1890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            ContentValues values) {
190bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson        try {
191bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            ICalendar.Property dtstartProperty =
192bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson                    component.getFirstProperty("DTSTART");
193bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            String dtstart = dtstartProperty.getValue();
194bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            ICalendar.Parameter tzidParam =
195bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson                    dtstartProperty.getFirstParameter("TZID");
196bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            // NOTE: the timezone may be null, if this is a floating time.
197bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            String tzid = tzidParam == null ? null : tzidParam.value;
198bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            Time start = new Time(tzidParam == null ? Time.TIMEZONE_UTC : tzid);
199bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            boolean inUtc = start.parse(dtstart);
200bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            boolean allDay = start.allDay;
201bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson
202bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            // We force TimeZone to UTC for "all day recurring events" as the server is sending no
203bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            // TimeZone in DTSTART for them
204bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            if (inUtc || allDay) {
205bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson                tzid = Time.TIMEZONE_UTC;
206bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            }
207e29edf9b71fbec8a7c7f0b523ca105a377632989Andy McFadden
208bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            String duration = computeDuration(start, component);
209bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            String rrule = flattenProperties(component, "RRULE");
210bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            String rdate = extractDates(component.getFirstProperty("RDATE"));
211bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            String exrule = flattenProperties(component, "EXRULE");
212bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            String exdate = extractDates(component.getFirstProperty("EXDATE"));
213bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson
214bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            if ((TextUtils.isEmpty(dtstart))||
215bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson                    (TextUtils.isEmpty(duration))||
216bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson                    ((TextUtils.isEmpty(rrule))&&
217bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson                            (TextUtils.isEmpty(rdate)))) {
218bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson                    if (false) {
219bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson                        Log.d(TAG, "Recurrence missing DTSTART, DTEND/DURATION, "
220bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson                                    + "or RRULE/RDATE: "
221bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson                                    + component.toString());
222bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson                    }
223bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson                    return false;
224bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            }
2250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
226bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            if (allDay) {
227bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson                start.timezone = Time.TIMEZONE_UTC;
228bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            }
229bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            long millis = start.toMillis(false /* use isDst */);
230bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            values.put(CalendarContract.Events.DTSTART, millis);
231bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            if (millis == -1) {
2320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                if (false) {
233bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson                    Log.d(TAG, "DTSTART is out of range: " + component.toString());
2340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                }
2350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return false;
2360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            }
237bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson
238bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            values.put(CalendarContract.Events.RRULE, rrule);
239bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            values.put(CalendarContract.Events.RDATE, rdate);
240bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            values.put(CalendarContract.Events.EXRULE, exrule);
241bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            values.put(CalendarContract.Events.EXDATE, exdate);
242bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            values.put(CalendarContract.Events.EVENT_TIMEZONE, tzid);
243bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            values.put(CalendarContract.Events.DURATION, duration);
244bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            values.put(CalendarContract.Events.ALL_DAY, allDay ? 1 : 0);
245bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            return true;
246bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson        } catch (TimeFormatException e) {
247bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            // Something is wrong with the format of this event
248bbcfb96060e9b518b810015984081a25c8ae2c26Isaac Katzenelson            Log.i(TAG,"Failed to parse event: " + component.toString());
2490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return false;
2500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
2510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
2520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
2530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    // This can be removed when the old CalendarSyncAdapter is removed.
2540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public static boolean populateComponent(Cursor cursor,
2550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                                            ICalendar.Component component) {
256e29edf9b71fbec8a7c7f0b523ca105a377632989Andy McFadden
2570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        int dtstartColumn = cursor.getColumnIndex(CalendarContract.Events.DTSTART);
2580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        int durationColumn = cursor.getColumnIndex(CalendarContract.Events.DURATION);
2590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        int tzidColumn = cursor.getColumnIndex(CalendarContract.Events.EVENT_TIMEZONE);
2600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        int rruleColumn = cursor.getColumnIndex(CalendarContract.Events.RRULE);
2610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        int rdateColumn = cursor.getColumnIndex(CalendarContract.Events.RDATE);
2620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        int exruleColumn = cursor.getColumnIndex(CalendarContract.Events.EXRULE);
2630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        int exdateColumn = cursor.getColumnIndex(CalendarContract.Events.EXDATE);
2640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        int allDayColumn = cursor.getColumnIndex(CalendarContract.Events.ALL_DAY);
2650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
2660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
2670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        long dtstart = -1;
2680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (!cursor.isNull(dtstartColumn)) {
2690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            dtstart = cursor.getLong(dtstartColumn);
2700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
2710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        String duration = cursor.getString(durationColumn);
2720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        String tzid = cursor.getString(tzidColumn);
2730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        String rruleStr = cursor.getString(rruleColumn);
2740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        String rdateStr = cursor.getString(rdateColumn);
2750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        String exruleStr = cursor.getString(exruleColumn);
2760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        String exdateStr = cursor.getString(exdateColumn);
2770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        boolean allDay = cursor.getInt(allDayColumn) == 1;
2780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
2790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if ((dtstart == -1) ||
2800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            (TextUtils.isEmpty(duration))||
2810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            ((TextUtils.isEmpty(rruleStr))&&
2820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                (TextUtils.isEmpty(rdateStr)))) {
2830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                // no recurrence.
2840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return false;
2850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
2860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
2870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        ICalendar.Property dtstartProp = new ICalendar.Property("DTSTART");
2880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        Time dtstartTime = null;
2890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (!TextUtils.isEmpty(tzid)) {
2900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            if (!allDay) {
2910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                dtstartProp.addParameter(new ICalendar.Parameter("TZID", tzid));
2920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            }
2930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            dtstartTime = new Time(tzid);
2940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        } else {
2950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            // use the "floating" timezone
2960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            dtstartTime = new Time(Time.TIMEZONE_UTC);
2970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
298e29edf9b71fbec8a7c7f0b523ca105a377632989Andy McFadden
2990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        dtstartTime.set(dtstart);
3000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        // make sure the time is printed just as a date, if all day.
3010d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        // TODO: android.pim.Time really should take care of this for us.
3020d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (allDay) {
3030d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            dtstartProp.addParameter(new ICalendar.Parameter("VALUE", "DATE"));
3040d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            dtstartTime.allDay = true;
3050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            dtstartTime.hour = 0;
3060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            dtstartTime.minute = 0;
3070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            dtstartTime.second = 0;
3080d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
3090d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
3100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        dtstartProp.setValue(dtstartTime.format2445());
3110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        component.addProperty(dtstartProp);
3120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        ICalendar.Property durationProp = new ICalendar.Property("DURATION");
3130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        durationProp.setValue(duration);
3140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        component.addProperty(durationProp);
3150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
3160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        addPropertiesForRuleStr(component, "RRULE", rruleStr);
3170d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        addPropertyForDateStr(component, "RDATE", rdateStr);
3180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        addPropertiesForRuleStr(component, "EXRULE", exruleStr);
3190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        addPropertyForDateStr(component, "EXDATE", exdateStr);
3200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        return true;
3210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
3220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
3230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenpublic static boolean populateComponent(ContentValues values,
3240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                                            ICalendar.Component component) {
3250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        long dtstart = -1;
3260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (values.containsKey(CalendarContract.Events.DTSTART)) {
3270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            dtstart = values.getAsLong(CalendarContract.Events.DTSTART);
3280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
3291546ea7c0d8791a690af9326904f1472321c95dfAlon Albert        final String duration = values.getAsString(CalendarContract.Events.DURATION);
3301546ea7c0d8791a690af9326904f1472321c95dfAlon Albert        final String tzid = values.getAsString(CalendarContract.Events.EVENT_TIMEZONE);
3311546ea7c0d8791a690af9326904f1472321c95dfAlon Albert        final String rruleStr = values.getAsString(CalendarContract.Events.RRULE);
3321546ea7c0d8791a690af9326904f1472321c95dfAlon Albert        final String rdateStr = values.getAsString(CalendarContract.Events.RDATE);
3331546ea7c0d8791a690af9326904f1472321c95dfAlon Albert        final String exruleStr = values.getAsString(CalendarContract.Events.EXRULE);
3341546ea7c0d8791a690af9326904f1472321c95dfAlon Albert        final String exdateStr = values.getAsString(CalendarContract.Events.EXDATE);
3351546ea7c0d8791a690af9326904f1472321c95dfAlon Albert        final Integer allDayInteger = values.getAsInteger(CalendarContract.Events.ALL_DAY);
3361546ea7c0d8791a690af9326904f1472321c95dfAlon Albert        final boolean allDay = (null != allDayInteger) ? (allDayInteger == 1) : false;
3370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
3380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if ((dtstart == -1) ||
3390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            (TextUtils.isEmpty(duration))||
3400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            ((TextUtils.isEmpty(rruleStr))&&
3410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                (TextUtils.isEmpty(rdateStr)))) {
3420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                // no recurrence.
3430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                return false;
3440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
3450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
3460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        ICalendar.Property dtstartProp = new ICalendar.Property("DTSTART");
3470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        Time dtstartTime = null;
3480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (!TextUtils.isEmpty(tzid)) {
3490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            if (!allDay) {
3500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                dtstartProp.addParameter(new ICalendar.Parameter("TZID", tzid));
3510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            }
3520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            dtstartTime = new Time(tzid);
3530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        } else {
3540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            // use the "floating" timezone
3550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            dtstartTime = new Time(Time.TIMEZONE_UTC);
3560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
3570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
3580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        dtstartTime.set(dtstart);
3590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        // make sure the time is printed just as a date, if all day.
3600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        // TODO: android.pim.Time really should take care of this for us.
3610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (allDay) {
3620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            dtstartProp.addParameter(new ICalendar.Parameter("VALUE", "DATE"));
3630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            dtstartTime.allDay = true;
3640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            dtstartTime.hour = 0;
3650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            dtstartTime.minute = 0;
3660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            dtstartTime.second = 0;
3670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
3680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
3690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        dtstartProp.setValue(dtstartTime.format2445());
3700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        component.addProperty(dtstartProp);
3710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        ICalendar.Property durationProp = new ICalendar.Property("DURATION");
3720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        durationProp.setValue(duration);
3730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        component.addProperty(durationProp);
3740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
3750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        addPropertiesForRuleStr(component, "RRULE", rruleStr);
3760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        addPropertyForDateStr(component, "RDATE", rdateStr);
3770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        addPropertiesForRuleStr(component, "EXRULE", exruleStr);
3780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        addPropertyForDateStr(component, "EXDATE", exdateStr);
3790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        return true;
3800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
3810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
3821546ea7c0d8791a690af9326904f1472321c95dfAlon Albert    public static void addPropertiesForRuleStr(ICalendar.Component component,
3830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                                                String propertyName,
3840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                                                String ruleStr) {
3850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (TextUtils.isEmpty(ruleStr)) {
3860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return;
3870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
3880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        String[] rrules = getRuleStrings(ruleStr);
3890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        for (String rrule : rrules) {
3900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            ICalendar.Property prop = new ICalendar.Property(propertyName);
3910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            prop.setValue(rrule);
3920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            component.addProperty(prop);
3930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
3940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
3950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
3960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static String[] getRuleStrings(String ruleStr) {
3970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (null == ruleStr) {
3980d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return new String[0];
3990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
4000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        String unfoldedRuleStr = unfold(ruleStr);
4010d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        String[] split = unfoldedRuleStr.split(RULE_SEPARATOR);
4020d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        int count = split.length;
4030d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        for (int n = 0; n < count; n++) {
4040d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            split[n] = fold(split[n]);
4050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
4060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        return split;
4070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
4080d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
4090d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
4100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static final Pattern IGNORABLE_ICAL_WHITESPACE_RE =
4110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            Pattern.compile("(?:\\r\\n?|\\n)[ \t]");
4120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
4130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static final Pattern FOLD_RE = Pattern.compile(".{75}");
4140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
4150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    /**
4160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    * fold and unfolds ical content lines as per RFC 2445 section 4.1.
4170d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    *
4180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    * <h3>4.1 Content Lines</h3>
4190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    *
4200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    * <p>The iCalendar object is organized into individual lines of text, called
4210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    * content lines. Content lines are delimited by a line break, which is a CRLF
4220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    * sequence (US-ASCII decimal 13, followed by US-ASCII decimal 10).
4230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    *
4240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    * <p>Lines of text SHOULD NOT be longer than 75 octets, excluding the line
4250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    * break. Long content lines SHOULD be split into a multiple line
4260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    * representations using a line "folding" technique. That is, a long line can
4270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    * be split between any two characters by inserting a CRLF immediately
4280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    * followed by a single linear white space character (i.e., SPACE, US-ASCII
4290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    * decimal 32 or HTAB, US-ASCII decimal 9). Any sequence of CRLF followed
4300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    * immediately by a single linear white space character is ignored (i.e.,
4310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    * removed) when processing the content type.
4320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    */
4330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public static String fold(String unfoldedIcalContent) {
4340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        return FOLD_RE.matcher(unfoldedIcalContent).replaceAll("$0\r\n ");
4350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
4360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
4370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    public static String unfold(String foldedIcalContent) {
4380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        return IGNORABLE_ICAL_WHITESPACE_RE.matcher(
4390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            foldedIcalContent).replaceAll("");
4400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
4410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
4421546ea7c0d8791a690af9326904f1472321c95dfAlon Albert    public static void addPropertyForDateStr(ICalendar.Component component,
4430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                                              String propertyName,
4440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                                              String dateStr) {
4450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (TextUtils.isEmpty(dateStr)) {
4460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return;
4470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
4480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
4490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        ICalendar.Property prop = new ICalendar.Property(propertyName);
4500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        String tz = null;
4510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        int tzidx = dateStr.indexOf(";");
4520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (tzidx != -1) {
4530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            tz = dateStr.substring(0, tzidx);
4540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            dateStr = dateStr.substring(tzidx + 1);
4550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
4560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (!TextUtils.isEmpty(tz)) {
4570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            prop.addParameter(new ICalendar.Parameter("TZID", tz));
4580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
4590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        prop.setValue(dateStr);
4600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        component.addProperty(prop);
4610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
462e29edf9b71fbec8a7c7f0b523ca105a377632989Andy McFadden
4630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static String computeDuration(Time start,
4640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                                          ICalendar.Component component) {
4650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        // see if a duration is defined
4660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        ICalendar.Property durationProperty =
4670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                component.getFirstProperty("DURATION");
4680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (durationProperty != null) {
4690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            // just return the duration
4700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return durationProperty.getValue();
4710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
4720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
4730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        // must compute a duration from the DTEND
4740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        ICalendar.Property dtendProperty =
4750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                component.getFirstProperty("DTEND");
4760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (dtendProperty == null) {
4770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            // no DURATION, no DTEND: 0 second duration
4780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return "+P0S";
4790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
4800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        ICalendar.Parameter endTzidParameter =
4810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                dtendProperty.getFirstParameter("TZID");
4820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        String endTzid = (endTzidParameter == null)
4830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                ? start.timezone : endTzidParameter.value;
4840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
4850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        Time end = new Time(endTzid);
4860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        end.parse(dtendProperty.getValue());
4870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        long durationMillis = end.toMillis(false /* use isDst */)
4880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                - start.toMillis(false /* use isDst */);
4890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        long durationSeconds = (durationMillis / 1000);
4900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (start.allDay && (durationSeconds % 86400) == 0) {
4910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return "P" + (durationSeconds / 86400) + "D"; // Server wants this instead of P86400S
4920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        } else {
4930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return "P" + durationSeconds + "S";
4940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
4950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
4960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
4970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static String flattenProperties(ICalendar.Component component,
4980d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                                            String name) {
4990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        List<ICalendar.Property> properties = component.getProperties(name);
5000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (properties == null || properties.isEmpty()) {
5010d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return null;
5020d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
5030d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
5040d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (properties.size() == 1) {
5050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return properties.get(0).getValue();
5060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
5070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
5080d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        StringBuilder sb = new StringBuilder();
5090d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
5100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        boolean first = true;
5110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        for (ICalendar.Property property : component.getProperties(name)) {
5120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            if (first) {
5130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                first = false;
5140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            } else {
5150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                // TODO: use commas.  our RECUR parsing should handle that
5160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                // anyway.
5170d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                sb.append(RULE_SEPARATOR);
5180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            }
5190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            sb.append(property.getValue());
5200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
5210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        return sb.toString();
5220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
5230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden
5240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    private static String extractDates(ICalendar.Property recurrence) {
5250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (recurrence == null) {
5260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return null;
5270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
5280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        ICalendar.Parameter tzidParam =
5290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden                recurrence.getFirstParameter("TZID");
5300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        if (tzidParam != null) {
5310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden            return tzidParam.value + ";" + recurrence.getValue();
5320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        }
5330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden        return recurrence.getValue();
5340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden    }
5350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden}
536