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