1f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank/*
2f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank * Copyright (C) 2010 The Android Open Source Project
3f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank *
4f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank * Licensed under the Apache License, Version 2.0 (the "License");
5f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank * you may not use this file except in compliance with the License.
6f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank * You may obtain a copy of the License at
7f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank *
8f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank *      http://www.apache.org/licenses/LICENSE-2.0
9f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank *
10f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank * Unless required by applicable law or agreed to in writing, software
11f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank * distributed under the License is distributed on an "AS IS" BASIS,
12f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank * See the License for the specific language governing permissions and
14f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank * limitations under the License.
15f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank */
16f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank
17f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blankpackage com.android.exchange.utility;
18f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank
1989be77eaea65211fea0ca32efd538318c7e049b3Marc Blankimport com.android.email.R;
20befa974de8b74877b2f1ea97e672d3eb2067a6d9Marc Blankimport com.android.email.Utility;
2189be77eaea65211fea0ca32efd538318c7e049b3Marc Blankimport com.android.email.mail.Address;
2289be77eaea65211fea0ca32efd538318c7e049b3Marc Blankimport com.android.email.provider.EmailContent.Account;
2389be77eaea65211fea0ca32efd538318c7e049b3Marc Blankimport com.android.email.provider.EmailContent.Attachment;
2489be77eaea65211fea0ca32efd538318c7e049b3Marc Blankimport com.android.email.provider.EmailContent.Message;
2589be77eaea65211fea0ca32efd538318c7e049b3Marc Blank
2689be77eaea65211fea0ca32efd538318c7e049b3Marc Blankimport android.content.ContentValues;
2789be77eaea65211fea0ca32efd538318c7e049b3Marc Blankimport android.content.Entity;
28bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blankimport android.content.res.Resources;
2989be77eaea65211fea0ca32efd538318c7e049b3Marc Blankimport android.provider.Calendar.Attendees;
3089be77eaea65211fea0ca32efd538318c7e049b3Marc Blankimport android.provider.Calendar.Events;
31f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blankimport android.test.AndroidTestCase;
32aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blankimport android.util.Log;
33f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank
346c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blankimport java.io.BufferedReader;
356c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blankimport java.io.IOException;
366c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blankimport java.io.StringReader;
37bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blankimport java.text.DateFormat;
386c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blankimport java.util.ArrayList;
394006e5fc224d097450fd66a321901b6636f7d9c4Marc Blankimport java.util.Calendar;
404577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blankimport java.util.Date;
414006e5fc224d097450fd66a321901b6636f7d9c4Marc Blankimport java.util.GregorianCalendar;
426c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blankimport java.util.HashMap;
43f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blankimport java.util.TimeZone;
44f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank
456f3013b78708321879728c28db044ab233cb2016Marc Blank/**
466f3013b78708321879728c28db044ab233cb2016Marc Blank * Tests of EAS Calendar Utilities
476f3013b78708321879728c28db044ab233cb2016Marc Blank * You can run this entire test case with:
486f3013b78708321879728c28db044ab233cb2016Marc Blank *   runtest -c com.android.exchange.utility.CalendarUtilitiesTests email
496f3013b78708321879728c28db044ab233cb2016Marc Blank *
506f3013b78708321879728c28db044ab233cb2016Marc Blank * Please see RFC2445 for RRULE definition
516f3013b78708321879728c28db044ab233cb2016Marc Blank * http://www.ietf.org/rfc/rfc2445.txt
526f3013b78708321879728c28db044ab233cb2016Marc Blank */
536f3013b78708321879728c28db044ab233cb2016Marc Blank
54f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blankpublic class CalendarUtilitiesTests extends AndroidTestCase {
55f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank
56f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank    // Some prebuilt time zones, Base64 encoded (as they arrive from EAS)
576f3013b78708321879728c28db044ab233cb2016Marc Blank    // More time zones to be added over time
586f3013b78708321879728c28db044ab233cb2016Marc Blank
596f3013b78708321879728c28db044ab233cb2016Marc Blank    // Not all time zones are appropriate for testing.  For example, ISRAEL_STANDARD_TIME cannot be
606f3013b78708321879728c28db044ab233cb2016Marc Blank    // used because DST is determined from year to year in a non-standard way (related to the lunar
616f3013b78708321879728c28db044ab233cb2016Marc Blank    // calendar); therefore, the test would only work during the year in which it was created
6282b2453b97c69d3cab169a7c00e4c410006ee068Marc Blank
6382b2453b97c69d3cab169a7c00e4c410006ee068Marc Blank    // This time zone has no DST
6482b2453b97c69d3cab169a7c00e4c410006ee068Marc Blank    private static final String ASIA_CALCUTTA_TIME =
65f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank        "tv7//0kAbgBkAGkAYQAgAFMAdABhAG4AZABhAHIAZAAgAFQAaQBtAGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
66f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank        "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEkAbgBkAGkAYQAgAEQAYQB5AGwAaQBnAGgAdAAgAFQAaQBtAGUA" +
67f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank        "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==";
6882b2453b97c69d3cab169a7c00e4c410006ee068Marc Blank
6982b2453b97c69d3cab169a7c00e4c410006ee068Marc Blank    // This time zone is equivalent to PST and uses DST
7082b2453b97c69d3cab169a7c00e4c410006ee068Marc Blank    private static final String AMERICA_DAWSON_TIME =
71f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank        "4AEAAFAAYQBjAGkAZgBpAGMAIABTAHQAYQBuAGQAYQByAGQAIABUAGkAbQBlAAAAAAAAAAAAAAAAAAAAAAAA" +
72f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank        "AAAAAAAAAAsAAAABAAIAAAAAAAAAAAAAAFAAYQBjAGkAZgBpAGMAIABEAGEAeQBsAGkAZwBoAHQAIABUAGkA" +
73f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank        "bQBlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAACAAIAAAAAAAAAxP///w==";
74f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank
7582b2453b97c69d3cab169a7c00e4c410006ee068Marc Blank    // Test a southern hemisphere time zone w/ DST
7682b2453b97c69d3cab169a7c00e4c410006ee068Marc Blank    private static final String AUSTRALIA_ACT_TIME =
7782b2453b97c69d3cab169a7c00e4c410006ee068Marc Blank        "qP3//0EAVQBTACAARQBhAHMAdABlAHIAbgAgAFMAdABhAG4AZABhAHIAZAAgAFQAaQBtAGUAAAAAAAAAAAAA" +
7882b2453b97c69d3cab169a7c00e4c410006ee068Marc Blank        "AAAAAAAAAAQAAAABAAMAAAAAAAAAAAAAAEEAVQBTACAARQBhAHMAdABlAHIAbgAgAEQAYQB5AGwAaQBnAGgA" +
7982b2453b97c69d3cab169a7c00e4c410006ee068Marc Blank        "dAAgAFQAaQBtAGUAAAAAAAAAAAAAAAAAAAAAAAoAAAABAAIAAAAAAAAAxP///w==";
8082b2453b97c69d3cab169a7c00e4c410006ee068Marc Blank
8182b2453b97c69d3cab169a7c00e4c410006ee068Marc Blank    // Test a european time zone w/ DST
8282b2453b97c69d3cab169a7c00e4c410006ee068Marc Blank    private static final String EUROPE_MOSCOW_TIME =
8382b2453b97c69d3cab169a7c00e4c410006ee068Marc Blank        "TP///1IAdQBzAHMAaQBhAG4AIABTAHQAYQBuAGQAYQByAGQAIABUAGkAbQBlAAAAAAAAAAAAAAAAAAAAAAAA" +
8482b2453b97c69d3cab169a7c00e4c410006ee068Marc Blank        "AAAAAAAAAAoAAAAFAAMAAAAAAAAAAAAAAFIAdQBzAHMAaQBhAG4AIABEAGEAeQBsAGkAZwBoAHQAIABUAGkA" +
8582b2453b97c69d3cab169a7c00e4c410006ee068Marc Blank        "bQBlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAFAAIAAAAAAAAAxP///w==";
8682b2453b97c69d3cab169a7c00e4c410006ee068Marc Blank
873e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank    // Test a timezone with GMT bias but bogus DST parameters (there is no equivalent time zone
883e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank    // in the database)
893e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank    private static final String GMT_UNKNOWN_DAYLIGHT_TIME =
903e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        "AAAAACgARwBNAFQAKwAwADAAOgAwADAAKQAgAFQAaQBtAGUAIABaAG8AbgBlAAAAAAAAAAAAAAAAAAAAAAAA" +
913e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        "AAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAACgARwBNAFQAKwAwADAAOgAwADAAKQAgAFQAaQBtAGUAIABaAG8A" +
923e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        "bgBlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAFAAEAAAAAAAAAxP///w==";
933e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank
94c55e2c884743bf5dee7de8d0fda69db3510476fdMarc Blank    // This time zone has no DST, but earlier, buggy code retrieved a TZ WITH DST
95c55e2c884743bf5dee7de8d0fda69db3510476fdMarc Blank    private static final String ARIZONA_TIME =
96c55e2c884743bf5dee7de8d0fda69db3510476fdMarc Blank        "pAEAAFUAUwAgAE0AbwB1AG4AdABhAGkAbgAgAFMAdABhAG4AZABhAHIAZAAgAFQAaQBtAGUAAAAAAAAAAAAA" +
97c55e2c884743bf5dee7de8d0fda69db3510476fdMarc Blank        "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFUAUwAgAE0AbwB1AG4AdABhAGkAbgAgAEQAYQB5AGwAaQBnAGgA" +
98c55e2c884743bf5dee7de8d0fda69db3510476fdMarc Blank        "dAAgAFQAaQBtAGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==";
99c55e2c884743bf5dee7de8d0fda69db3510476fdMarc Blank
100bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank    private static final String ORGANIZER = "organizer@server.com";
101bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank    private static final String ATTENDEE = "attendee@server.com";
102bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank
103f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank    public void testGetSet() {
104f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank        byte[] bytes = new byte[] {0, 1, 2, 3, 4, 5, 6, 7};
105f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank
106f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank        // First, check that getWord/Long are properly little endian
107f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank        assertEquals(0x0100, CalendarUtilities.getWord(bytes, 0));
108f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank        assertEquals(0x03020100, CalendarUtilities.getLong(bytes, 0));
109f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank        assertEquals(0x07060504, CalendarUtilities.getLong(bytes, 4));
110f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank
111f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank        // Set some words and longs
112f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank        CalendarUtilities.setWord(bytes, 0, 0xDEAD);
113f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank        CalendarUtilities.setLong(bytes, 2, 0xBEEFBEEF);
114f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank        CalendarUtilities.setWord(bytes, 6, 0xCEDE);
115f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank
116f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank        // Retrieve them
117f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank        assertEquals(0xDEAD, CalendarUtilities.getWord(bytes, 0));
118f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank        assertEquals(0xBEEFBEEF, CalendarUtilities.getLong(bytes, 2));
119f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank        assertEquals(0xCEDE, CalendarUtilities.getWord(bytes, 6));
120f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank    }
121f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank
122f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank    public void testParseTimeZoneEndToEnd() {
12382b2453b97c69d3cab169a7c00e4c410006ee068Marc Blank        TimeZone tz = CalendarUtilities.tziStringToTimeZone(AMERICA_DAWSON_TIME);
12482b2453b97c69d3cab169a7c00e4c410006ee068Marc Blank        assertEquals("America/Dawson", tz.getID());
12582b2453b97c69d3cab169a7c00e4c410006ee068Marc Blank        tz = CalendarUtilities.tziStringToTimeZone(ASIA_CALCUTTA_TIME);
12682b2453b97c69d3cab169a7c00e4c410006ee068Marc Blank        assertEquals("Asia/Calcutta", tz.getID());
12782b2453b97c69d3cab169a7c00e4c410006ee068Marc Blank        tz = CalendarUtilities.tziStringToTimeZone(AUSTRALIA_ACT_TIME);
12882b2453b97c69d3cab169a7c00e4c410006ee068Marc Blank        assertEquals("Australia/ACT", tz.getID());
12982b2453b97c69d3cab169a7c00e4c410006ee068Marc Blank        tz = CalendarUtilities.tziStringToTimeZone(EUROPE_MOSCOW_TIME);
13082b2453b97c69d3cab169a7c00e4c410006ee068Marc Blank        assertEquals("Europe/Moscow", tz.getID());
1313e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        tz = CalendarUtilities.tziStringToTimeZone(GMT_UNKNOWN_DAYLIGHT_TIME);
1323e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        int bias = tz.getOffset(System.currentTimeMillis());
1333e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        assertEquals(0, bias);
134c55e2c884743bf5dee7de8d0fda69db3510476fdMarc Blank        // Make sure non-DST TZ's work properly
135c55e2c884743bf5dee7de8d0fda69db3510476fdMarc Blank        tz = CalendarUtilities.tziStringToTimeZone(ARIZONA_TIME);
136c55e2c884743bf5dee7de8d0fda69db3510476fdMarc Blank        assertEquals("America/Phoenix", tz.getID());
137f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank    }
138f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank
139a8259394596eda7aed2b9edfdb30ff03c34acb66Marc Blank    public void testGenerateEasDayOfWeek() {
140befa974de8b74877b2f1ea97e672d3eb2067a6d9Marc Blank        String byDay = "TU,WE,SA";
1416f3013b78708321879728c28db044ab233cb2016Marc Blank        // TU = 4, WE = 8; SA = 64;
142a8259394596eda7aed2b9edfdb30ff03c34acb66Marc Blank        assertEquals("76", CalendarUtilities.generateEasDayOfWeek(byDay));
1436f3013b78708321879728c28db044ab233cb2016Marc Blank        // MO = 2, TU = 4; WE = 8; TH = 16; FR = 32
144befa974de8b74877b2f1ea97e672d3eb2067a6d9Marc Blank        byDay = "MO,TU,WE,TH,FR";
145a8259394596eda7aed2b9edfdb30ff03c34acb66Marc Blank        assertEquals("62", CalendarUtilities.generateEasDayOfWeek(byDay));
1466f3013b78708321879728c28db044ab233cb2016Marc Blank        // SU = 1
147a8259394596eda7aed2b9edfdb30ff03c34acb66Marc Blank        byDay = "SU";
148a8259394596eda7aed2b9edfdb30ff03c34acb66Marc Blank        assertEquals("1", CalendarUtilities.generateEasDayOfWeek(byDay));
149a8259394596eda7aed2b9edfdb30ff03c34acb66Marc Blank    }
150a8259394596eda7aed2b9edfdb30ff03c34acb66Marc Blank
151a8259394596eda7aed2b9edfdb30ff03c34acb66Marc Blank    public void testTokenFromRrule() {
152a8259394596eda7aed2b9edfdb30ff03c34acb66Marc Blank        String rrule = "FREQ=DAILY;INTERVAL=1;BYDAY=WE,TH,SA;BYMONTHDAY=17";
153a8259394596eda7aed2b9edfdb30ff03c34acb66Marc Blank        assertEquals("DAILY", CalendarUtilities.tokenFromRrule(rrule, "FREQ="));
154a8259394596eda7aed2b9edfdb30ff03c34acb66Marc Blank        assertEquals("1", CalendarUtilities.tokenFromRrule(rrule, "INTERVAL="));
155a8259394596eda7aed2b9edfdb30ff03c34acb66Marc Blank        assertEquals("17", CalendarUtilities.tokenFromRrule(rrule, "BYMONTHDAY="));
156befa974de8b74877b2f1ea97e672d3eb2067a6d9Marc Blank        assertEquals("WE,TH,SA", CalendarUtilities.tokenFromRrule(rrule, "BYDAY="));
157a8259394596eda7aed2b9edfdb30ff03c34acb66Marc Blank        assertNull(CalendarUtilities.tokenFromRrule(rrule, "UNTIL="));
158a8259394596eda7aed2b9edfdb30ff03c34acb66Marc Blank    }
159a8259394596eda7aed2b9edfdb30ff03c34acb66Marc Blank
160cf7581c51c39aad1e8e955bfde62335871a7fd29Marc Blank    public void testRecurrenceUntilToEasUntil() {
161cf274512ed722cd039e21ab05ce172052740116fMarc Blank        // Test full format
162cf274512ed722cd039e21ab05ce172052740116fMarc Blank        assertEquals("YYYYMMDDT000000Z",
163cf7581c51c39aad1e8e955bfde62335871a7fd29Marc Blank                CalendarUtilities.recurrenceUntilToEasUntil("YYYYMMDDTHHMMSSZ"));
164cf7581c51c39aad1e8e955bfde62335871a7fd29Marc Blank        // Test date only format
165cf274512ed722cd039e21ab05ce172052740116fMarc Blank        assertEquals("YYYYMMDDT000000Z",
166cf7581c51c39aad1e8e955bfde62335871a7fd29Marc Blank                CalendarUtilities.recurrenceUntilToEasUntil("YYYYMMDD"));
167cf7581c51c39aad1e8e955bfde62335871a7fd29Marc Blank    }
168cf7581c51c39aad1e8e955bfde62335871a7fd29Marc Blank
1694006e5fc224d097450fd66a321901b6636f7d9c4Marc Blank    public void testParseEmailDateTimeToMillis(String date) {
1704006e5fc224d097450fd66a321901b6636f7d9c4Marc Blank        // Format for email date strings is 2010-02-23T16:00:00.000Z
1714006e5fc224d097450fd66a321901b6636f7d9c4Marc Blank        String dateString = "2010-02-23T15:16:17.000Z";
172989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        long dateTime = Utility.parseEmailDateTimeToMillis(dateString);
1734006e5fc224d097450fd66a321901b6636f7d9c4Marc Blank        GregorianCalendar cal = new GregorianCalendar();
1744006e5fc224d097450fd66a321901b6636f7d9c4Marc Blank        cal.setTimeInMillis(dateTime);
1754006e5fc224d097450fd66a321901b6636f7d9c4Marc Blank        cal.setTimeZone(TimeZone.getTimeZone("GMT"));
1764006e5fc224d097450fd66a321901b6636f7d9c4Marc Blank        assertEquals(cal.get(Calendar.YEAR), 2010);
1774006e5fc224d097450fd66a321901b6636f7d9c4Marc Blank        assertEquals(cal.get(Calendar.MONTH), 1);  // 0 based
1784006e5fc224d097450fd66a321901b6636f7d9c4Marc Blank        assertEquals(cal.get(Calendar.DAY_OF_MONTH), 23);
1794006e5fc224d097450fd66a321901b6636f7d9c4Marc Blank        assertEquals(cal.get(Calendar.HOUR_OF_DAY), 16);
1804006e5fc224d097450fd66a321901b6636f7d9c4Marc Blank        assertEquals(cal.get(Calendar.MINUTE), 16);
1814006e5fc224d097450fd66a321901b6636f7d9c4Marc Blank        assertEquals(cal.get(Calendar.SECOND), 17);
1824006e5fc224d097450fd66a321901b6636f7d9c4Marc Blank    }
1834006e5fc224d097450fd66a321901b6636f7d9c4Marc Blank
1844006e5fc224d097450fd66a321901b6636f7d9c4Marc Blank    public void testParseDateTimeToMillis(String date) {
1854006e5fc224d097450fd66a321901b6636f7d9c4Marc Blank        // Format for calendar date strings is 20100223T160000000Z
1864006e5fc224d097450fd66a321901b6636f7d9c4Marc Blank        String dateString = "20100223T151617000Z";
187989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda        long dateTime = Utility.parseDateTimeToMillis(dateString);
1884006e5fc224d097450fd66a321901b6636f7d9c4Marc Blank        GregorianCalendar cal = new GregorianCalendar();
1894006e5fc224d097450fd66a321901b6636f7d9c4Marc Blank        cal.setTimeInMillis(dateTime);
1904006e5fc224d097450fd66a321901b6636f7d9c4Marc Blank        cal.setTimeZone(TimeZone.getTimeZone("GMT"));
1914006e5fc224d097450fd66a321901b6636f7d9c4Marc Blank        assertEquals(cal.get(Calendar.YEAR), 2010);
1924006e5fc224d097450fd66a321901b6636f7d9c4Marc Blank        assertEquals(cal.get(Calendar.MONTH), 1);  // 0 based
1934006e5fc224d097450fd66a321901b6636f7d9c4Marc Blank        assertEquals(cal.get(Calendar.DAY_OF_MONTH), 23);
1944006e5fc224d097450fd66a321901b6636f7d9c4Marc Blank        assertEquals(cal.get(Calendar.HOUR_OF_DAY), 16);
1954006e5fc224d097450fd66a321901b6636f7d9c4Marc Blank        assertEquals(cal.get(Calendar.MINUTE), 16);
1964006e5fc224d097450fd66a321901b6636f7d9c4Marc Blank        assertEquals(cal.get(Calendar.SECOND), 17);
1974006e5fc224d097450fd66a321901b6636f7d9c4Marc Blank    }
198cf7581c51c39aad1e8e955bfde62335871a7fd29Marc Blank
1996c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank    private Entity setupTestEventEntity(String organizer, String attendee, String title) {
20089be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        // Create an Entity for an Event
20189be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        ContentValues entityValues = new ContentValues();
20289be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        Entity entity = new Entity(entityValues);
20389be77eaea65211fea0ca32efd538318c7e049b3Marc Blank
20489be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        // Set up values for the Event
20589be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        String location = "Meeting Location";
20689be77eaea65211fea0ca32efd538318c7e049b3Marc Blank
20789be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        // Fill in times, location, title, and organizer
20889be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        entityValues.put("DTSTAMP",
20989be77eaea65211fea0ca32efd538318c7e049b3Marc Blank                CalendarUtilities.convertEmailDateTimeToCalendarDateTime("2010-04-05T14:30:51Z"));
21089be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        entityValues.put(Events.DTSTART,
211989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda                Utility.parseEmailDateTimeToMillis("2010-04-12T18:30:00Z"));
21289be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        entityValues.put(Events.DTEND,
213989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda                Utility.parseEmailDateTimeToMillis("2010-04-12T19:30:00Z"));
21489be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        entityValues.put(Events.EVENT_LOCATION, location);
21589be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        entityValues.put(Events.TITLE, title);
21689be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        entityValues.put(Events.ORGANIZER, organizer);
2176c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        entityValues.put(Events._SYNC_DATA, "31415926535");
21889be77eaea65211fea0ca32efd538318c7e049b3Marc Blank
21989be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        // Add the attendee
22089be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        ContentValues attendeeValues = new ContentValues();
22189be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        attendeeValues.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE);
22289be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        attendeeValues.put(Attendees.ATTENDEE_EMAIL, attendee);
22389be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        entity.addSubValue(Attendees.CONTENT_URI, attendeeValues);
22489be77eaea65211fea0ca32efd538318c7e049b3Marc Blank
22589be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        // Add the organizer
22689be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        ContentValues organizerValues = new ContentValues();
22789be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        organizerValues.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ORGANIZER);
22889be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        organizerValues.put(Attendees.ATTENDEE_EMAIL, organizer);
22989be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        entity.addSubValue(Attendees.CONTENT_URI, organizerValues);
2306c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        return entity;
2316c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank    }
23289be77eaea65211fea0ca32efd538318c7e049b3Marc Blank
23382e51e06312c76f37900c7b1718af863caeff9afMarc Blank    private Entity setupTestExceptionEntity(String organizer, String attendee, String title) {
23482e51e06312c76f37900c7b1718af863caeff9afMarc Blank        Entity entity = setupTestEventEntity(organizer, attendee, title);
23582e51e06312c76f37900c7b1718af863caeff9afMarc Blank        ContentValues entityValues = entity.getEntityValues();
23682e51e06312c76f37900c7b1718af863caeff9afMarc Blank        entityValues.put(Events.ORIGINAL_EVENT, 69);
23782e51e06312c76f37900c7b1718af863caeff9afMarc Blank        // The exception will be on April 26th
23882e51e06312c76f37900c7b1718af863caeff9afMarc Blank        entityValues.put(Events.ORIGINAL_INSTANCE_TIME,
23982e51e06312c76f37900c7b1718af863caeff9afMarc Blank                Utility.parseEmailDateTimeToMillis("2010-04-26T18:30:00Z"));
24082e51e06312c76f37900c7b1718af863caeff9afMarc Blank        return entity;
24182e51e06312c76f37900c7b1718af863caeff9afMarc Blank    }
24282e51e06312c76f37900c7b1718af863caeff9afMarc Blank
2436c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank    public void testCreateMessageForEntity_Reply() {
2446c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        // Set up the "event"
2456c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        String title = "Discuss Unit Tests";
246bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        Entity entity = setupTestEventEntity(ORGANIZER, ATTENDEE, title);
24789be77eaea65211fea0ca32efd538318c7e049b3Marc Blank
2486c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        // Create a dummy account for the attendee
2496c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        Account account = new Account();
250bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        account.mEmailAddress = ATTENDEE;
25189be77eaea65211fea0ca32efd538318c7e049b3Marc Blank
2526c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        // The uid is required, but can be anything
2536c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        String uid = "31415926535";
2546c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank
25589be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        // Create the outgoing message
25689be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        Message msg = CalendarUtilities.createMessageForEntity(mContext, entity,
25789be77eaea65211fea0ca32efd538318c7e049b3Marc Blank                Message.FLAG_OUTGOING_MEETING_ACCEPT, uid, account);
25889be77eaea65211fea0ca32efd538318c7e049b3Marc Blank
25989be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        // First, we should have a message
26089be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        assertNotNull(msg);
26189be77eaea65211fea0ca32efd538318c7e049b3Marc Blank
26289be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        // Now check some of the fields of the message
263bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        assertEquals(Address.pack(new Address[] {new Address(ORGANIZER)}), msg.mTo);
264a6e1f8d4562ed437d67378935c8be6cbfe9b86f9Marc Blank        Resources resources = getContext().getResources();
265a6e1f8d4562ed437d67378935c8be6cbfe9b86f9Marc Blank        String accept = resources.getString(R.string.meeting_accepted, title);
2664577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        assertEquals(accept, msg.mSubject);
267a6e1f8d4562ed437d67378935c8be6cbfe9b86f9Marc Blank        assertNotNull(msg.mText);
268a6e1f8d4562ed437d67378935c8be6cbfe9b86f9Marc Blank        assertTrue(msg.mText.contains(resources.getString(R.string.meeting_where, "")));
26989be77eaea65211fea0ca32efd538318c7e049b3Marc Blank
27089be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        // And make sure we have an attachment
27189be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        assertNotNull(msg.mAttachments);
27289be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        assertEquals(1, msg.mAttachments.size());
27389be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        Attachment att = msg.mAttachments.get(0);
27489be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        // And that the attachment has the correct elements
27589be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        assertEquals("invite.ics", att.mFileName);
2760ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank        assertEquals(Attachment.FLAG_ICS_ALTERNATIVE_PART,
2770ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank                att.mFlags & Attachment.FLAG_ICS_ALTERNATIVE_PART);
27889be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        assertEquals("text/calendar; method=REPLY", att.mMimeType);
27920225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki        assertNotNull(att.mContentBytes);
28020225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki        assertEquals(att.mSize, att.mContentBytes.length);
28189be77eaea65211fea0ca32efd538318c7e049b3Marc Blank
28289be77eaea65211fea0ca32efd538318c7e049b3Marc Blank        //TODO Check the contents of the attachment using an iCalendar parser
28389be77eaea65211fea0ca32efd538318c7e049b3Marc Blank    }
28489be77eaea65211fea0ca32efd538318c7e049b3Marc Blank
2851c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank    public void testCreateMessageForEntity_Invite_AllDay() throws IOException {
2861c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        // Set up the "event"
2871c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        String title = "Discuss Unit Tests";
2881c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        Entity entity = setupTestEventEntity(ORGANIZER, ATTENDEE, title);
2891c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        entity.getEntityValues().put(Events.ALL_DAY, 1);
2901c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank
2911c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        // Create a dummy account for the attendee
2921c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        Account account = new Account();
2931c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        account.mEmailAddress = ORGANIZER;
2941c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank
2951c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        // The uid is required, but can be anything
2961c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        String uid = "31415926535";
2971c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank
2981c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        // Create the outgoing message
2991c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        Message msg = CalendarUtilities.createMessageForEntity(mContext, entity,
3001c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank                Message.FLAG_OUTGOING_MEETING_INVITE, uid, account);
3011c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank
3021c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        // First, we should have a message
3031c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        assertNotNull(msg);
3041c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank
3051c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        // Now check some of the fields of the message
3061c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        assertEquals(Address.pack(new Address[] {new Address(ATTENDEE)}), msg.mTo);
30778021cbbf8154898ce4e68d8c2d2871568cda5e3Marc Blank        assertEquals(title, msg.mSubject);
3081c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank
3091c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        // And make sure we have an attachment
3101c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        assertNotNull(msg.mAttachments);
3111c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        assertEquals(1, msg.mAttachments.size());
3121c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        Attachment att = msg.mAttachments.get(0);
3131c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        // And that the attachment has the correct elements
3141c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        assertEquals("invite.ics", att.mFileName);
3151c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        assertEquals(Attachment.FLAG_ICS_ALTERNATIVE_PART,
3161c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank                att.mFlags & Attachment.FLAG_ICS_ALTERNATIVE_PART);
3171c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        assertEquals("text/calendar; method=REQUEST", att.mMimeType);
3181c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        assertNotNull(att.mContentBytes);
3191c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        assertEquals(att.mSize, att.mContentBytes.length);
3201c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank
3211c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        // We'll check the contents of the ics file here
3221c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        BlockHash vcalendar = parseIcsContent(att.mContentBytes);
3231c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        assertNotNull(vcalendar);
3241c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank
3251c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        // We should have a VCALENDAR with a REQUEST method
3261c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        assertEquals("VCALENDAR", vcalendar.name);
3271c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        assertEquals("REQUEST", vcalendar.get("METHOD"));
3281c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank
3291c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        // We should have one block under VCALENDAR
3301c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        assertEquals(1, vcalendar.blocks.size());
3311c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        BlockHash vevent = vcalendar.blocks.get(0);
3321c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        // It's a VEVENT with the following fields
3331c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        assertEquals("VEVENT", vevent.name);
3341c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        assertEquals("Meeting Location", vevent.get("LOCATION"));
3351c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        assertEquals("0", vevent.get("SEQUENCE"));
3361c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        assertEquals("Discuss Unit Tests", vevent.get("SUMMARY"));
3371c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        assertEquals(uid, vevent.get("UID"));
3381c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        assertEquals("MAILTO:" + ATTENDEE,
3391c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank                vevent.get("ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE"));
3401c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank
3411c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        // These next two fields should have a date only
3421c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        assertEquals("20100412", vevent.get("DTSTART;VALUE=DATE"));
3431c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        assertEquals("20100412", vevent.get("DTEND;VALUE=DATE"));
3441c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        // This should be set to TRUE for all-day events
3451c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        assertEquals("TRUE", vevent.get("X-MICROSOFT-CDO-ALLDAYEVENT"));
3461c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank    }
3471c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank
3486c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank    public void testCreateMessageForEntity_Invite() throws IOException {
3496c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        // Set up the "event"
3506c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        String title = "Discuss Unit Tests";
351bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        Entity entity = setupTestEventEntity(ORGANIZER, ATTENDEE, title);
3526c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank
3536c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        // Create a dummy account for the attendee
3546c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        Account account = new Account();
355bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        account.mEmailAddress = ORGANIZER;
3566c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank
3576c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        // The uid is required, but can be anything
3586c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        String uid = "31415926535";
3596c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank
3606c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        // Create the outgoing message
3616c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        Message msg = CalendarUtilities.createMessageForEntity(mContext, entity,
3626c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                Message.FLAG_OUTGOING_MEETING_INVITE, uid, account);
3636c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank
3646c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        // First, we should have a message
3656c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        assertNotNull(msg);
3666c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank
3676c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        // Now check some of the fields of the message
368bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        assertEquals(Address.pack(new Address[] {new Address(ATTENDEE)}), msg.mTo);
36978021cbbf8154898ce4e68d8c2d2871568cda5e3Marc Blank        assertEquals(title, msg.mSubject);
3706c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank
3716c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        // And make sure we have an attachment
3726c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        assertNotNull(msg.mAttachments);
3736c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        assertEquals(1, msg.mAttachments.size());
3746c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        Attachment att = msg.mAttachments.get(0);
3756c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        // And that the attachment has the correct elements
3766c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        assertEquals("invite.ics", att.mFileName);
3770ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank        assertEquals(Attachment.FLAG_ICS_ALTERNATIVE_PART,
3780ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank                att.mFlags & Attachment.FLAG_ICS_ALTERNATIVE_PART);
3796c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        assertEquals("text/calendar; method=REQUEST", att.mMimeType);
38020225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki        assertNotNull(att.mContentBytes);
38120225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki        assertEquals(att.mSize, att.mContentBytes.length);
3826c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank
3836c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        // We'll check the contents of the ics file here
38420225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki        BlockHash vcalendar = parseIcsContent(att.mContentBytes);
3856c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        assertNotNull(vcalendar);
3866c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank
3876c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        // We should have a VCALENDAR with a REQUEST method
3886c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        assertEquals("VCALENDAR", vcalendar.name);
3896c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        assertEquals("REQUEST", vcalendar.get("METHOD"));
3906c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank
3916c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        // We should have one block under VCALENDAR
3926c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        assertEquals(1, vcalendar.blocks.size());
3936c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        BlockHash vevent = vcalendar.blocks.get(0);
3946c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        // It's a VEVENT with the following fields
3956c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        assertEquals("VEVENT", vevent.name);
3966c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        assertEquals("Meeting Location", vevent.get("LOCATION"));
3976c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        assertEquals("0", vevent.get("SEQUENCE"));
3986c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        assertEquals("Discuss Unit Tests", vevent.get("SUMMARY"));
3996c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        assertEquals(uid, vevent.get("UID"));
400bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        assertEquals("MAILTO:" + ATTENDEE,
4016c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                vevent.get("ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE"));
4021c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank
4031c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        // These next two fields should exist (without the VALUE=DATE suffix)
4041c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        assertNotNull(vevent.get("DTSTART"));
4051c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        assertNotNull(vevent.get("DTEND"));
4061c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        assertNull(vevent.get("DTSTART;VALUE=DATE"));
4071c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        assertNull(vevent.get("DTEND;VALUE=DATE"));
4081c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        // This shouldn't exist for this event
4091c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        assertNull(vevent.get("X-MICROSOFT-CDO-ALLDAYEVENT"));
4106c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank    }
4116c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank
4129294d8c030039070d173acb9907b64a48d4b165fMarc Blank    public void testCreateMessageForEntity_Recurring() throws IOException {
4139294d8c030039070d173acb9907b64a48d4b165fMarc Blank        // Set up the "event"
4149294d8c030039070d173acb9907b64a48d4b165fMarc Blank        String title = "Discuss Unit Tests";
4159294d8c030039070d173acb9907b64a48d4b165fMarc Blank        Entity entity = setupTestEventEntity(ORGANIZER, ATTENDEE, title);
4169294d8c030039070d173acb9907b64a48d4b165fMarc Blank        // Set up a RRULE for this event
4179294d8c030039070d173acb9907b64a48d4b165fMarc Blank        entity.getEntityValues().put(Events.RRULE, "FREQ=DAILY");
4189294d8c030039070d173acb9907b64a48d4b165fMarc Blank
4199294d8c030039070d173acb9907b64a48d4b165fMarc Blank        // Create a dummy account for the attendee
4209294d8c030039070d173acb9907b64a48d4b165fMarc Blank        Account account = new Account();
4219294d8c030039070d173acb9907b64a48d4b165fMarc Blank        account.mEmailAddress = ORGANIZER;
4229294d8c030039070d173acb9907b64a48d4b165fMarc Blank
4239294d8c030039070d173acb9907b64a48d4b165fMarc Blank        // The uid is required, but can be anything
4249294d8c030039070d173acb9907b64a48d4b165fMarc Blank        String uid = "31415926535";
4259294d8c030039070d173acb9907b64a48d4b165fMarc Blank
4269294d8c030039070d173acb9907b64a48d4b165fMarc Blank        // Create the outgoing message
4279294d8c030039070d173acb9907b64a48d4b165fMarc Blank        Message msg = CalendarUtilities.createMessageForEntity(mContext, entity,
4289294d8c030039070d173acb9907b64a48d4b165fMarc Blank                Message.FLAG_OUTGOING_MEETING_INVITE, uid, account);
4299294d8c030039070d173acb9907b64a48d4b165fMarc Blank
4309294d8c030039070d173acb9907b64a48d4b165fMarc Blank        // First, we should have a message
4319294d8c030039070d173acb9907b64a48d4b165fMarc Blank        assertNotNull(msg);
4329294d8c030039070d173acb9907b64a48d4b165fMarc Blank
4339294d8c030039070d173acb9907b64a48d4b165fMarc Blank        // Now check some of the fields of the message
4349294d8c030039070d173acb9907b64a48d4b165fMarc Blank        assertEquals(Address.pack(new Address[] {new Address(ATTENDEE)}), msg.mTo);
4359294d8c030039070d173acb9907b64a48d4b165fMarc Blank        assertEquals(title, msg.mSubject);
4369294d8c030039070d173acb9907b64a48d4b165fMarc Blank
4379294d8c030039070d173acb9907b64a48d4b165fMarc Blank        // And make sure we have an attachment
4389294d8c030039070d173acb9907b64a48d4b165fMarc Blank        assertNotNull(msg.mAttachments);
4399294d8c030039070d173acb9907b64a48d4b165fMarc Blank        assertEquals(1, msg.mAttachments.size());
4409294d8c030039070d173acb9907b64a48d4b165fMarc Blank        Attachment att = msg.mAttachments.get(0);
4419294d8c030039070d173acb9907b64a48d4b165fMarc Blank        // And that the attachment has the correct elements
4429294d8c030039070d173acb9907b64a48d4b165fMarc Blank        assertEquals("invite.ics", att.mFileName);
4439294d8c030039070d173acb9907b64a48d4b165fMarc Blank        assertEquals(Attachment.FLAG_ICS_ALTERNATIVE_PART,
4449294d8c030039070d173acb9907b64a48d4b165fMarc Blank                att.mFlags & Attachment.FLAG_ICS_ALTERNATIVE_PART);
4459294d8c030039070d173acb9907b64a48d4b165fMarc Blank        assertEquals("text/calendar; method=REQUEST", att.mMimeType);
4469294d8c030039070d173acb9907b64a48d4b165fMarc Blank        assertNotNull(att.mContentBytes);
4479294d8c030039070d173acb9907b64a48d4b165fMarc Blank        assertEquals(att.mSize, att.mContentBytes.length);
4489294d8c030039070d173acb9907b64a48d4b165fMarc Blank
4499294d8c030039070d173acb9907b64a48d4b165fMarc Blank        // We'll check the contents of the ics file here
4509294d8c030039070d173acb9907b64a48d4b165fMarc Blank        BlockHash vcalendar = parseIcsContent(att.mContentBytes);
4519294d8c030039070d173acb9907b64a48d4b165fMarc Blank        assertNotNull(vcalendar);
4529294d8c030039070d173acb9907b64a48d4b165fMarc Blank
4539294d8c030039070d173acb9907b64a48d4b165fMarc Blank        // We should have a VCALENDAR with a REQUEST method
4549294d8c030039070d173acb9907b64a48d4b165fMarc Blank        assertEquals("VCALENDAR", vcalendar.name);
4559294d8c030039070d173acb9907b64a48d4b165fMarc Blank        assertEquals("REQUEST", vcalendar.get("METHOD"));
4569294d8c030039070d173acb9907b64a48d4b165fMarc Blank
4579294d8c030039070d173acb9907b64a48d4b165fMarc Blank        // We should have two blocks under VCALENDAR (VTIMEZONE and VEVENT)
4589294d8c030039070d173acb9907b64a48d4b165fMarc Blank        assertEquals(2, vcalendar.blocks.size());
4599294d8c030039070d173acb9907b64a48d4b165fMarc Blank
4609294d8c030039070d173acb9907b64a48d4b165fMarc Blank        // This is the time zone that should be used
4619294d8c030039070d173acb9907b64a48d4b165fMarc Blank        TimeZone timeZone = TimeZone.getDefault();
4629294d8c030039070d173acb9907b64a48d4b165fMarc Blank
4639294d8c030039070d173acb9907b64a48d4b165fMarc Blank        BlockHash vtimezone = vcalendar.blocks.get(0);
4649294d8c030039070d173acb9907b64a48d4b165fMarc Blank        // It should be a VTIMEZONE for timeZone
4659294d8c030039070d173acb9907b64a48d4b165fMarc Blank        assertEquals("VTIMEZONE", vtimezone.name);
4669294d8c030039070d173acb9907b64a48d4b165fMarc Blank        assertEquals(timeZone.getID(), vtimezone.get("TZID"));
4679294d8c030039070d173acb9907b64a48d4b165fMarc Blank
4689294d8c030039070d173acb9907b64a48d4b165fMarc Blank        BlockHash vevent = vcalendar.blocks.get(1);
4699294d8c030039070d173acb9907b64a48d4b165fMarc Blank        // It's a VEVENT with the following fields
4709294d8c030039070d173acb9907b64a48d4b165fMarc Blank        assertEquals("VEVENT", vevent.name);
4719294d8c030039070d173acb9907b64a48d4b165fMarc Blank        assertEquals("Meeting Location", vevent.get("LOCATION"));
4729294d8c030039070d173acb9907b64a48d4b165fMarc Blank        assertEquals("0", vevent.get("SEQUENCE"));
4739294d8c030039070d173acb9907b64a48d4b165fMarc Blank        assertEquals("Discuss Unit Tests", vevent.get("SUMMARY"));
4749294d8c030039070d173acb9907b64a48d4b165fMarc Blank        assertEquals(uid, vevent.get("UID"));
4759294d8c030039070d173acb9907b64a48d4b165fMarc Blank        assertEquals("MAILTO:" + ATTENDEE,
4769294d8c030039070d173acb9907b64a48d4b165fMarc Blank                vevent.get("ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE"));
4779294d8c030039070d173acb9907b64a48d4b165fMarc Blank
4789294d8c030039070d173acb9907b64a48d4b165fMarc Blank        // We should have DTSTART/DTEND with time zone
4799294d8c030039070d173acb9907b64a48d4b165fMarc Blank        assertNotNull(vevent.get("DTSTART;TZID=" + timeZone.getID()));
4809294d8c030039070d173acb9907b64a48d4b165fMarc Blank        assertNotNull(vevent.get("DTEND;TZID=" + timeZone.getID()));
4819294d8c030039070d173acb9907b64a48d4b165fMarc Blank        assertNull(vevent.get("DTSTART"));
4829294d8c030039070d173acb9907b64a48d4b165fMarc Blank        assertNull(vevent.get("DTEND"));
4839294d8c030039070d173acb9907b64a48d4b165fMarc Blank        assertNull(vevent.get("DTSTART;VALUE=DATE"));
4849294d8c030039070d173acb9907b64a48d4b165fMarc Blank        assertNull(vevent.get("DTEND;VALUE=DATE"));
4859294d8c030039070d173acb9907b64a48d4b165fMarc Blank        // This shouldn't exist for this event
4869294d8c030039070d173acb9907b64a48d4b165fMarc Blank        assertNull(vevent.get("X-MICROSOFT-CDO-ALLDAYEVENT"));
4879294d8c030039070d173acb9907b64a48d4b165fMarc Blank    }
4889294d8c030039070d173acb9907b64a48d4b165fMarc Blank
48982e51e06312c76f37900c7b1718af863caeff9afMarc Blank    public void testCreateMessageForEntity_Exception_Cancel() throws IOException {
49082e51e06312c76f37900c7b1718af863caeff9afMarc Blank        // Set up the "exception"...
49182e51e06312c76f37900c7b1718af863caeff9afMarc Blank        String title = "Discuss Unit Tests";
492bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        Entity entity = setupTestExceptionEntity(ORGANIZER, ATTENDEE, title);
49382e51e06312c76f37900c7b1718af863caeff9afMarc Blank
49482e51e06312c76f37900c7b1718af863caeff9afMarc Blank        ContentValues entityValues = entity.getEntityValues();
49582e51e06312c76f37900c7b1718af863caeff9afMarc Blank        // Mark the Exception as dirty
49682e51e06312c76f37900c7b1718af863caeff9afMarc Blank        entityValues.put(Events._SYNC_DIRTY, 1);
49782e51e06312c76f37900c7b1718af863caeff9afMarc Blank        // And mark it canceled
49882e51e06312c76f37900c7b1718af863caeff9afMarc Blank        entityValues.put(Events.STATUS, Events.STATUS_CANCELED);
49982e51e06312c76f37900c7b1718af863caeff9afMarc Blank
50082e51e06312c76f37900c7b1718af863caeff9afMarc Blank        // Create a dummy account for the attendee
50182e51e06312c76f37900c7b1718af863caeff9afMarc Blank        Account account = new Account();
502bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        account.mEmailAddress = ORGANIZER;
50382e51e06312c76f37900c7b1718af863caeff9afMarc Blank
50482e51e06312c76f37900c7b1718af863caeff9afMarc Blank        // The uid is required, but can be anything
50582e51e06312c76f37900c7b1718af863caeff9afMarc Blank        String uid = "31415926535";
50682e51e06312c76f37900c7b1718af863caeff9afMarc Blank
50782e51e06312c76f37900c7b1718af863caeff9afMarc Blank        // Create the outgoing message
50882e51e06312c76f37900c7b1718af863caeff9afMarc Blank        Message msg = CalendarUtilities.createMessageForEntity(mContext, entity,
50939ea302126b6d205ae561a9c3f568b75aa6a202aMarc Blank                Message.FLAG_OUTGOING_MEETING_CANCEL, uid, account);
51082e51e06312c76f37900c7b1718af863caeff9afMarc Blank
51182e51e06312c76f37900c7b1718af863caeff9afMarc Blank        // First, we should have a message
51282e51e06312c76f37900c7b1718af863caeff9afMarc Blank        assertNotNull(msg);
51382e51e06312c76f37900c7b1718af863caeff9afMarc Blank
51482e51e06312c76f37900c7b1718af863caeff9afMarc Blank        // Now check some of the fields of the message
515bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        assertEquals(Address.pack(new Address[] {new Address(ATTENDEE)}), msg.mTo);
51639ea302126b6d205ae561a9c3f568b75aa6a202aMarc Blank        String cancel = getContext().getResources().getString(R.string.meeting_canceled, title);
51739ea302126b6d205ae561a9c3f568b75aa6a202aMarc Blank        assertEquals(cancel, msg.mSubject);
51882e51e06312c76f37900c7b1718af863caeff9afMarc Blank
51982e51e06312c76f37900c7b1718af863caeff9afMarc Blank        // And make sure we have an attachment
52082e51e06312c76f37900c7b1718af863caeff9afMarc Blank        assertNotNull(msg.mAttachments);
52182e51e06312c76f37900c7b1718af863caeff9afMarc Blank        assertEquals(1, msg.mAttachments.size());
52282e51e06312c76f37900c7b1718af863caeff9afMarc Blank        Attachment att = msg.mAttachments.get(0);
52382e51e06312c76f37900c7b1718af863caeff9afMarc Blank        // And that the attachment has the correct elements
52482e51e06312c76f37900c7b1718af863caeff9afMarc Blank        assertEquals("invite.ics", att.mFileName);
5250ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank        assertEquals(Attachment.FLAG_ICS_ALTERNATIVE_PART,
5260ed690bfb4fdfa86cb6f75c19e24b8ff39a1756cMarc Blank                att.mFlags & Attachment.FLAG_ICS_ALTERNATIVE_PART);
527700b373007da10b456e1f9cb0fde2a021fdee84cMarc Blank        assertEquals("text/calendar; method=CANCEL", att.mMimeType);
52820225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki        assertNotNull(att.mContentBytes);
52982e51e06312c76f37900c7b1718af863caeff9afMarc Blank
53082e51e06312c76f37900c7b1718af863caeff9afMarc Blank        // We'll check the contents of the ics file here
53120225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki        BlockHash vcalendar = parseIcsContent(att.mContentBytes);
53282e51e06312c76f37900c7b1718af863caeff9afMarc Blank        assertNotNull(vcalendar);
53382e51e06312c76f37900c7b1718af863caeff9afMarc Blank
534700b373007da10b456e1f9cb0fde2a021fdee84cMarc Blank        // We should have a VCALENDAR with a CANCEL method
53582e51e06312c76f37900c7b1718af863caeff9afMarc Blank        assertEquals("VCALENDAR", vcalendar.name);
536700b373007da10b456e1f9cb0fde2a021fdee84cMarc Blank        assertEquals("CANCEL", vcalendar.get("METHOD"));
53782e51e06312c76f37900c7b1718af863caeff9afMarc Blank
53882e51e06312c76f37900c7b1718af863caeff9afMarc Blank        // This is the time zone that should be used
53982e51e06312c76f37900c7b1718af863caeff9afMarc Blank        TimeZone timeZone = TimeZone.getDefault();
54082e51e06312c76f37900c7b1718af863caeff9afMarc Blank
54182e51e06312c76f37900c7b1718af863caeff9afMarc Blank        // We should have two blocks under VCALENDAR (VTIMEZONE and VEVENT)
54282e51e06312c76f37900c7b1718af863caeff9afMarc Blank        assertEquals(2, vcalendar.blocks.size());
54382e51e06312c76f37900c7b1718af863caeff9afMarc Blank
54482e51e06312c76f37900c7b1718af863caeff9afMarc Blank        BlockHash vtimezone = vcalendar.blocks.get(0);
54582e51e06312c76f37900c7b1718af863caeff9afMarc Blank        // It should be a VTIMEZONE for timeZone
54682e51e06312c76f37900c7b1718af863caeff9afMarc Blank        assertEquals("VTIMEZONE", vtimezone.name);
54782e51e06312c76f37900c7b1718af863caeff9afMarc Blank        assertEquals(timeZone.getID(), vtimezone.get("TZID"));
54882e51e06312c76f37900c7b1718af863caeff9afMarc Blank
54982e51e06312c76f37900c7b1718af863caeff9afMarc Blank        BlockHash vevent = vcalendar.blocks.get(1);
55082e51e06312c76f37900c7b1718af863caeff9afMarc Blank        // It's a VEVENT with the following fields
55182e51e06312c76f37900c7b1718af863caeff9afMarc Blank        assertEquals("VEVENT", vevent.name);
55282e51e06312c76f37900c7b1718af863caeff9afMarc Blank        assertEquals("Meeting Location", vevent.get("LOCATION"));
55382e51e06312c76f37900c7b1718af863caeff9afMarc Blank        assertEquals("0", vevent.get("SEQUENCE"));
55482e51e06312c76f37900c7b1718af863caeff9afMarc Blank        assertEquals("Discuss Unit Tests", vevent.get("SUMMARY"));
55582e51e06312c76f37900c7b1718af863caeff9afMarc Blank        assertEquals(uid, vevent.get("UID"));
556bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        assertEquals("MAILTO:" + ATTENDEE,
55739ea302126b6d205ae561a9c3f568b75aa6a202aMarc Blank                vevent.get("ATTENDEE;ROLE=REQ-PARTICIPANT"));
55882e51e06312c76f37900c7b1718af863caeff9afMarc Blank        long originalTime = entityValues.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
55982e51e06312c76f37900c7b1718af863caeff9afMarc Blank        assertNotSame(0, originalTime);
56082e51e06312c76f37900c7b1718af863caeff9afMarc Blank        // For an exception, RECURRENCE-ID is critical
5611c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank        assertEquals(CalendarUtilities.millisToEasDateTime(originalTime, timeZone,
5621c48450c02ea33e498e17a1bfc9f95576d3027b6Marc Blank                true /*withTime*/), vevent.get("RECURRENCE-ID" + ";TZID=" + timeZone.getID()));
56382e51e06312c76f37900c7b1718af863caeff9afMarc Blank    }
56482e51e06312c76f37900c7b1718af863caeff9afMarc Blank
5654577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank    public void testUtcOffsetString() {
5664577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        assertEquals(CalendarUtilities.utcOffsetString(540), "+0900");
5674577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        assertEquals(CalendarUtilities.utcOffsetString(-480), "-0800");
5684577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        assertEquals(CalendarUtilities.utcOffsetString(0), "+0000");
5694577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank    }
5706c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank
5714577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank    public void testFindTransitionDate() {
5724577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        // We'll find some transitions and make sure that we're properly in or out of daylight time
5734577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        // on either side of the transition.
5744577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        // Use CST for testing (any other will do as well, as long as it has DST)
5754577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        TimeZone tz = TimeZone.getTimeZone("US/Central");
5764577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        // Get a calendar at January 1st of the current year
5774577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        GregorianCalendar calendar = new GregorianCalendar(tz);
5784577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        calendar.set(CalendarUtilities.sCurrentYear, Calendar.JANUARY, 1);
5794577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        // Get start and end times at start and end of year
5804577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        long startTime = calendar.getTimeInMillis();
5814577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        long endTime = startTime + (365*CalendarUtilities.DAYS);
5824577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        // Find the first transition
5834577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        GregorianCalendar transitionCalendar =
5844577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank            CalendarUtilities.findTransitionDate(tz, startTime, endTime, false);
5854577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        long transitionTime = transitionCalendar.getTimeInMillis();
5864577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        // Before should be in standard time; after in daylight time
5874577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        Date beforeDate = new Date(transitionTime - CalendarUtilities.HOURS);
5884577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        Date afterDate = new Date(transitionTime + CalendarUtilities.HOURS);
5894577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        assertFalse(tz.inDaylightTime(beforeDate));
5904577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        assertTrue(tz.inDaylightTime(afterDate));
5914577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank
5924577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        // Find the next one...
5934577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        transitionCalendar = CalendarUtilities.findTransitionDate(tz, transitionTime +
5944577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank                CalendarUtilities.DAYS, endTime, true);
5954577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        transitionTime = transitionCalendar.getTimeInMillis();
5964577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        // This time, Before should be in daylight time; after in standard time
5974577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        beforeDate = new Date(transitionTime - CalendarUtilities.HOURS);
5984577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        afterDate = new Date(transitionTime + CalendarUtilities.HOURS);
5994577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        assertTrue(tz.inDaylightTime(beforeDate));
6004577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        assertFalse(tz.inDaylightTime(afterDate));
6014577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank
6024577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        // Captain Renault: What in heaven's name brought you to Casablanca?
6034577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        // Rick: My health. I came to Casablanca for the waters.
6044577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        // Also, they have no daylight savings time
6054577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        tz = TimeZone.getTimeZone("Africa/Casablanca");
6064577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        // Get a calendar at January 1st of the current year
6074577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        calendar = new GregorianCalendar(tz);
6084577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        calendar.set(CalendarUtilities.sCurrentYear, Calendar.JANUARY, 1);
6094577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        // Get start and end times at start and end of year
6104577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        startTime = calendar.getTimeInMillis();
6114577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        endTime = startTime + (365*CalendarUtilities.DAYS);
6124577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        // Find the first transition
6134577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        transitionCalendar = CalendarUtilities.findTransitionDate(tz, startTime, endTime, false);
6144577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        // There had better not be one
6154577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        assertNull(transitionCalendar);
6164577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank    }
6176f3013b78708321879728c28db044ab233cb2016Marc Blank
6184577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank    public void testRruleFromRecurrence() {
6194577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        // Every Monday for 2 weeks
6204577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        String rrule = CalendarUtilities.rruleFromRecurrence(
6214577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank                1 /*Weekly*/, 2 /*Occurrences*/, 1 /*Interval*/, 2 /*Monday*/, 0, 0, 0, null);
6224577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        assertEquals("FREQ=WEEKLY;INTERVAL=1;COUNT=2;BYDAY=MO", rrule);
6234577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        // Every Tuesday and Friday
6244577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        rrule = CalendarUtilities.rruleFromRecurrence(
6254577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank                1 /*Weekly*/, 0 /*Occurrences*/, 0 /*Interval*/, 36 /*Tue&Fri*/, 0, 0, 0, null);
6264577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        assertEquals("FREQ=WEEKLY;BYDAY=TU,FR", rrule);
6274577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        // The last Saturday of the month
6284577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        rrule = CalendarUtilities.rruleFromRecurrence(
6294577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank                3 /*Monthly/DayofWeek*/, 0, 0, 64 /*Sat*/, 0, 5 /*Last*/, 0, null);
6304577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        assertEquals("FREQ=MONTHLY;BYDAY=-1SA", rrule);
6314577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        // The third Wednesday and Thursday of the month
6324577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        rrule = CalendarUtilities.rruleFromRecurrence(
6334577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank                3 /*Monthly/DayofWeek*/, 0, 0, 24 /*Wed&Thu*/, 0, 3 /*3rd*/, 0, null);
6344577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        assertEquals("FREQ=MONTHLY;BYDAY=3WE,3TH", rrule);
6354577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        // The 14th of the every month
6364577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        rrule = CalendarUtilities.rruleFromRecurrence(
6374577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank                2 /*Monthly/Date*/, 0, 0, 0, 14 /*14th*/, 0, 0, null);
6384577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        assertEquals("FREQ=MONTHLY;BYMONTHDAY=14", rrule);
6394577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        // Every 31st of October
6404577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        rrule = CalendarUtilities.rruleFromRecurrence(
6414577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank                5 /*Yearly/Date*/, 0, 0, 0, 31 /*31st*/, 0, 10 /*October*/, null);
6424577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        assertEquals("FREQ=YEARLY;BYMONTHDAY=31;BYMONTH=10", rrule);
6434577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        // The first Tuesday of June
6444577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        rrule = CalendarUtilities.rruleFromRecurrence(
6454577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank                6 /*Yearly/Month/DayOfWeek*/, 0, 0, 4 /*Tue*/, 0, 1 /*1st*/, 6 /*June*/, null);
6464577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank        assertEquals("FREQ=YEARLY;BYDAY=1TU;BYMONTH=6", rrule);
6474577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank    }
6484577f71f76c94dc9fcb06efd2656970925dd3f6aMarc Blank
6496c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank    /**
6506c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank     * For debugging purposes, to help keep track of parsing errors.
6516c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank     */
6526c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank    private class UnterminatedBlockException extends IOException {
6536c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        private static final long serialVersionUID = 1L;
6546c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        UnterminatedBlockException(String name) {
6556c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank            super(name);
6566c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        }
6576c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank    }
6586c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank
6596c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank    /**
6606c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank     * A lightweight representation of block object containing a hash of individual values and an
6616c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank     * array of inner blocks.  The object is build by pulling elements from a BufferedReader.
6626c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank     * NOTE: Multiple values of a given field are not supported.  We'd see this with ATTENDEEs, for
6636c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank     * example, and possibly RDATEs in VTIMEZONEs without an RRULE; these cases will be handled
6646c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank     * at a later time.
6656c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank     */
6666c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank    private class BlockHash {
6676c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        String name;
6686c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        HashMap<String, String> hash = new HashMap<String, String>();
6696c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        ArrayList<BlockHash> blocks = new ArrayList<BlockHash>();
6706c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank
6716c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        BlockHash (String _name, BufferedReader reader) throws IOException {
6726c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank            name = _name;
6736c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank            String lastField = null;
6746c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank            String lastValue = null;
6756c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank            while (true) {
6766c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                // Get a line; we're done if it's null
6776c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                String line = reader.readLine();
6786c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                if (line == null) {
6796c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                    throw new UnterminatedBlockException(name);
6806c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                }
6816c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                int length = line.length();
6826c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                if (length == 0) {
6836c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                    // We shouldn't ever see an empty line
6846c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                    throw new IllegalArgumentException();
6856c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                }
68688a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki                // A line starting with tab is a continuation
68788a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki                if (line.charAt(0) == '\t') {
6886c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                    // Remember the line and length
6896c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                    lastValue = line.substring(1);
6906c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                    // Save the concatenation of old and new values
6916c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                    hash.put(lastField, hash.get(lastField) + lastValue);
6926c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                    continue;
6936c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                }
6946c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                // Find the field delimiter
6956c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                int pos = line.indexOf(':');
6966c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                // If not found, or at EOL, this is a bad ics
6976c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                if (pos < 0 || pos >= length) {
6986c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                    throw new IllegalArgumentException();
6996c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                }
7006c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                // Remember the field, value, and length
7016c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                lastField = line.substring(0, pos);
7026c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                lastValue = line.substring(pos + 1);
7036c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                if (lastField.equals("BEGIN")) {
7046c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                    blocks.add(new BlockHash(lastValue, reader));
7056c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                    continue;
7066c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                } else if (lastField.equals("END")) {
7076c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                    if (!lastValue.equals(name)) {
7086c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                        throw new UnterminatedBlockException(name);
7096c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                    }
7106c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                    break;
7116c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                }
7126c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank
7136c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                // Save it away and continue
7146c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank                hash.put(lastField, lastValue);
7156c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank            }
7166c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        }
7176c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank
7186c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        String get(String field) {
7196c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank            return hash.get(field);
7206c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        }
7216c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank    }
7226c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank
72320225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki    private BlockHash parseIcsContent(byte[] bytes) throws IOException {
724fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki        BufferedReader reader = new BufferedReader(new StringReader(Utility.fromUtf8(bytes)));
7256c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        String line = reader.readLine();
7266c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        if (!line.equals("BEGIN:VCALENDAR")) {
7276c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank            throw new IllegalArgumentException();
7286c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        }
7296c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank        return new BlockHash("VCALENDAR", reader);
7306c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank    }
7316c8b1ec088a1276ef5e18dd7137e18eb141dc5acMarc Blank
732bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank    public void testBuildMessageTextFromEntityValues() {
733bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        // Set up a test event
734bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        String title = "Event Title";
735bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        Entity entity = setupTestEventEntity(ORGANIZER, ATTENDEE, title);
736bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        ContentValues entityValues = entity.getEntityValues();
737bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank
738bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        // Save this away; we'll use it a few times below
739bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        Resources resources = mContext.getResources();
740bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        Date date = new Date(entityValues.getAsLong(Events.DTSTART));
741bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        String dateTimeString = DateFormat.getDateTimeInstance().format(date);
742bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank
743bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        // Get the text for this message
744bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        StringBuilder sb = new StringBuilder();
745bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        CalendarUtilities.buildMessageTextFromEntityValues(mContext, entityValues, sb);
746bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        String text = sb.toString();
747bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        // We'll just check the when and where
748bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        assertTrue(text.contains(resources.getString(R.string.meeting_when, dateTimeString)));
749bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        String location = entityValues.getAsString(Events.EVENT_LOCATION);
750bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        assertTrue(text.contains(resources.getString(R.string.meeting_where, location)));
751bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank
752bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        // Make this event recurring
753bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        entity.getEntityValues().put(Events.RRULE, "FREQ=WEEKLY;BYDAY=MO");
754bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        sb = new StringBuilder();
755bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        CalendarUtilities.buildMessageTextFromEntityValues(mContext, entityValues, sb);
756bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        text = sb.toString();
757bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank        assertTrue(text.contains(resources.getString(R.string.meeting_recurring, dateTimeString)));
758bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank    }
759bf916efe955e07aba2a5bce9eecae325f84d4934Marc Blank
760aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank    /**
761aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank     * Sanity test for time zone generation.  Most important, make sure that we can run through
762aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank     * all of the time zones without generating an exception.  Second, make sure that we're finding
763aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank     * rules for at least 90% of time zones that use daylight time (empirically, it's more like
764aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank     * 95%).  Log those without rules.
765aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank     * @throws IOException
766aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank     */
767aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank    public void testTimeZoneToVTimezone() throws IOException {
768aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank        SimpleIcsWriter writer = new SimpleIcsWriter();
769aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank        int rule = 0;
770aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank        int nodst = 0;
771aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank        int norule = 0;
772aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank        ArrayList<String> norulelist = new ArrayList<String>();
773aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank        for (String tzs: TimeZone.getAvailableIDs()) {
774aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank            TimeZone tz = TimeZone.getTimeZone(tzs);
775aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank            writer = new SimpleIcsWriter();
776aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank            CalendarUtilities.timeZoneToVTimezone(tz, writer);
777aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank            String vc = writer.toString();
778aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank            boolean hasRule = vc.indexOf("RRULE") > 0;
779aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank            if (hasRule) {
780aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank                rule++;
781aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank            } else if (tz.useDaylightTime()) {
782aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank                norule++;
783aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank                norulelist.add(tz.getID());
784aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank            } else {
785aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank                nodst++;
786aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank            }
787aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank        }
788aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank        assertTrue(norule < rule/10);
789aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank        Log.d("TimeZoneGeneration",
790aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank                "Rule: " + rule + ", No DST: " + nodst + ", No rule: " + norule);
791aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank        for (String nr: norulelist) {
792aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank            Log.d("TimeZoneGeneration", "No rule: " + nr);
793aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank        }
794aec526f141f49658fdc5535c76bdf8c879ae95b9Marc Blank    }
7957e30b2e22f597b3e4b902c333a8dafc8a21e8b86Marc Blank
7967e30b2e22f597b3e4b902c333a8dafc8a21e8b86Marc Blank    public void testGetUidFromGlobalObjId() {
7977e30b2e22f597b3e4b902c333a8dafc8a21e8b86Marc Blank        // This is a "foreign" uid (from some vCalendar client)
7987e30b2e22f597b3e4b902c333a8dafc8a21e8b86Marc Blank        String globalObjId = "BAAAAIIA4AB0xbcQGoLgCAAAAAAAAAAAAAAAAAAAAAAAAAAAMQAAA" +
7997e30b2e22f597b3e4b902c333a8dafc8a21e8b86Marc Blank                "HZDYWwtVWlkAQAAADI3NjU1NmRkLTg1MzAtNGZiZS1iMzE0LThiM2JlYTYwMjE0OQA=";
8007e30b2e22f597b3e4b902c333a8dafc8a21e8b86Marc Blank        String uid = CalendarUtilities.getUidFromGlobalObjId(globalObjId);
8017e30b2e22f597b3e4b902c333a8dafc8a21e8b86Marc Blank        assertEquals(uid, "276556dd-8530-4fbe-b314-8b3bea602149");
8027e30b2e22f597b3e4b902c333a8dafc8a21e8b86Marc Blank        // This is a native EAS uid
8037e30b2e22f597b3e4b902c333a8dafc8a21e8b86Marc Blank        globalObjId =
8047e30b2e22f597b3e4b902c333a8dafc8a21e8b86Marc Blank            "BAAAAIIA4AB0xbcQGoLgCAAAAADACTu7KbPKAQAAAAAAAAAAEAAAAObgsG6HVt1Fmy+7GlLbGhY=";
8057e30b2e22f597b3e4b902c333a8dafc8a21e8b86Marc Blank        uid = CalendarUtilities.getUidFromGlobalObjId(globalObjId);
8067e30b2e22f597b3e4b902c333a8dafc8a21e8b86Marc Blank        assertEquals(uid, "040000008200E00074C5B7101A82E00800000000C0093BBB29B3CA" +
8077e30b2e22f597b3e4b902c333a8dafc8a21e8b86Marc Blank                "01000000000000000010000000E6E0B06E8756DD459B2FBB1A52DB1A16");
8087e30b2e22f597b3e4b902c333a8dafc8a21e8b86Marc Blank    }
809095eee41565900a2509256ffcbf6cd67a4c4e345Marc Blank
810095eee41565900a2509256ffcbf6cd67a4c4e345Marc Blank    public void testSelfAttendeeStatusFromBusyStatus() {
811095eee41565900a2509256ffcbf6cd67a4c4e345Marc Blank        assertEquals(Attendees.ATTENDEE_STATUS_ACCEPTED,
8128a19af3739aad25d26754e8a52e986cc38b41db6Marc Blank                CalendarUtilities.attendeeStatusFromBusyStatus(
813095eee41565900a2509256ffcbf6cd67a4c4e345Marc Blank                        CalendarUtilities.BUSY_STATUS_BUSY));
814095eee41565900a2509256ffcbf6cd67a4c4e345Marc Blank        assertEquals(Attendees.ATTENDEE_STATUS_TENTATIVE,
8158a19af3739aad25d26754e8a52e986cc38b41db6Marc Blank                CalendarUtilities.attendeeStatusFromBusyStatus(
816095eee41565900a2509256ffcbf6cd67a4c4e345Marc Blank                        CalendarUtilities.BUSY_STATUS_TENTATIVE));
817095eee41565900a2509256ffcbf6cd67a4c4e345Marc Blank        assertEquals(Attendees.ATTENDEE_STATUS_NONE,
8188a19af3739aad25d26754e8a52e986cc38b41db6Marc Blank                CalendarUtilities.attendeeStatusFromBusyStatus(
819095eee41565900a2509256ffcbf6cd67a4c4e345Marc Blank                        CalendarUtilities.BUSY_STATUS_FREE));
820095eee41565900a2509256ffcbf6cd67a4c4e345Marc Blank        assertEquals(Attendees.ATTENDEE_STATUS_NONE,
8218a19af3739aad25d26754e8a52e986cc38b41db6Marc Blank                CalendarUtilities.attendeeStatusFromBusyStatus(
8221880ad6a83120b89ed239858d27d3e63242160e6Marc Blank                        CalendarUtilities.BUSY_STATUS_OUT_OF_OFFICE));
823095eee41565900a2509256ffcbf6cd67a4c4e345Marc Blank    }
824095eee41565900a2509256ffcbf6cd67a4c4e345Marc Blank
825095eee41565900a2509256ffcbf6cd67a4c4e345Marc Blank    public void testBusyStatusFromSelfStatus() {
826095eee41565900a2509256ffcbf6cd67a4c4e345Marc Blank        assertEquals(CalendarUtilities.BUSY_STATUS_FREE,
8278a19af3739aad25d26754e8a52e986cc38b41db6Marc Blank                CalendarUtilities.busyStatusFromAttendeeStatus(
828095eee41565900a2509256ffcbf6cd67a4c4e345Marc Blank                        Attendees.ATTENDEE_STATUS_DECLINED));
8291880ad6a83120b89ed239858d27d3e63242160e6Marc Blank        assertEquals(CalendarUtilities.BUSY_STATUS_FREE,
8308a19af3739aad25d26754e8a52e986cc38b41db6Marc Blank                CalendarUtilities.busyStatusFromAttendeeStatus(
831095eee41565900a2509256ffcbf6cd67a4c4e345Marc Blank                        Attendees.ATTENDEE_STATUS_NONE));
8321880ad6a83120b89ed239858d27d3e63242160e6Marc Blank        assertEquals(CalendarUtilities.BUSY_STATUS_FREE,
8338a19af3739aad25d26754e8a52e986cc38b41db6Marc Blank                CalendarUtilities.busyStatusFromAttendeeStatus(
834095eee41565900a2509256ffcbf6cd67a4c4e345Marc Blank                        Attendees.ATTENDEE_STATUS_INVITED));
835095eee41565900a2509256ffcbf6cd67a4c4e345Marc Blank        assertEquals(CalendarUtilities.BUSY_STATUS_TENTATIVE,
8368a19af3739aad25d26754e8a52e986cc38b41db6Marc Blank                CalendarUtilities.busyStatusFromAttendeeStatus(
837095eee41565900a2509256ffcbf6cd67a4c4e345Marc Blank                        Attendees.ATTENDEE_STATUS_TENTATIVE));
838095eee41565900a2509256ffcbf6cd67a4c4e345Marc Blank        assertEquals(CalendarUtilities.BUSY_STATUS_BUSY,
8398a19af3739aad25d26754e8a52e986cc38b41db6Marc Blank                CalendarUtilities.busyStatusFromAttendeeStatus(
840095eee41565900a2509256ffcbf6cd67a4c4e345Marc Blank                        Attendees.ATTENDEE_STATUS_ACCEPTED));
841095eee41565900a2509256ffcbf6cd67a4c4e345Marc Blank    }
8423e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank
8433e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank    public void testGetUtcAllDayCalendarTime() {
8443e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        GregorianCalendar correctUtc = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
8453e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        correctUtc.set(2011, 2, 10, 0, 0, 0);
8463e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        long correctUtcTime = correctUtc.getTimeInMillis();
8473e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank
8483e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        TimeZone localTimeZone = TimeZone.getTimeZone("GMT-0700");
8493e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        GregorianCalendar localCalendar = new GregorianCalendar(localTimeZone);
8503e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        localCalendar.set(2011, 2, 10, 12, 23, 34);
8513e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        long localTimeMillis = localCalendar.getTimeInMillis();
8523e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        long convertedUtcTime =
8533e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank            CalendarUtilities.getUtcAllDayCalendarTime(localTimeMillis, localTimeZone);
8543e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        // Milliseconds aren't zeroed out and may not be the same
8553e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        assertEquals(convertedUtcTime/1000, correctUtcTime/1000);
8563e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank
8573e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        localTimeZone = TimeZone.getTimeZone("GMT+0700");
8583e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        localCalendar = new GregorianCalendar(localTimeZone);
8593e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        localCalendar.set(2011, 2, 10, 12, 23, 34);
8603e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        localTimeMillis = localCalendar.getTimeInMillis();
8613e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        convertedUtcTime =
8623e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank            CalendarUtilities.getUtcAllDayCalendarTime(localTimeMillis, localTimeZone);
8633e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        assertEquals(convertedUtcTime/1000, correctUtcTime/1000);
8643e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank    }
8653e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank
8663e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank    public void testGetLocalAllDayCalendarTime() {
8673e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        TimeZone utcTimeZone = TimeZone.getTimeZone("UTC");
8683e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        TimeZone localTimeZone = TimeZone.getTimeZone("GMT-0700");
8693e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        GregorianCalendar correctLocal = new GregorianCalendar(localTimeZone);
8703e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        correctLocal.set(2011, 2, 10, 0, 0, 0);
8713e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        long correctLocalTime = correctLocal.getTimeInMillis();
8723e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank
8733e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        GregorianCalendar utcCalendar = new GregorianCalendar(utcTimeZone);
8743e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        utcCalendar.set(2011, 2, 10, 12, 23, 34);
8753e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        long utcTimeMillis = utcCalendar.getTimeInMillis();
8763e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        long convertedLocalTime =
8773e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank            CalendarUtilities.getLocalAllDayCalendarTime(utcTimeMillis, localTimeZone);
8783e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        // Milliseconds aren't zeroed out and may not be the same
8793e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        assertEquals(convertedLocalTime/1000, correctLocalTime/1000);
8803e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank
8813e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        localTimeZone = TimeZone.getTimeZone("GMT+0700");
8823e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        correctLocal = new GregorianCalendar(localTimeZone);
8833e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        correctLocal.set(2011, 2, 10, 0, 0, 0);
8843e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        correctLocalTime = correctLocal.getTimeInMillis();
8853e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank
8863e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        utcCalendar = new GregorianCalendar(utcTimeZone);
8873e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        utcCalendar.set(2011, 2, 10, 12, 23, 34);
8883e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        utcTimeMillis = utcCalendar.getTimeInMillis();
8893e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        convertedLocalTime =
8903e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank            CalendarUtilities.getLocalAllDayCalendarTime(utcTimeMillis, localTimeZone);
8913e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        // Milliseconds aren't zeroed out and may not be the same
8923e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank        assertEquals(convertedLocalTime/1000, correctLocalTime/1000);
8933e065170f3374ecb78af2e68bcb28040b83fd928Marc Blank    }
894efcde27bf74acdd82bb0aad0ad1fd0b767ca5f18Andy Stadler
895efcde27bf74acdd82bb0aad0ad1fd0b767ca5f18Andy Stadler    public void testGetIntegerValueAsBoolean() {
896efcde27bf74acdd82bb0aad0ad1fd0b767ca5f18Andy Stadler        ContentValues cv = new ContentValues();
897efcde27bf74acdd82bb0aad0ad1fd0b767ca5f18Andy Stadler        cv.put("A", 1);
898efcde27bf74acdd82bb0aad0ad1fd0b767ca5f18Andy Stadler        cv.put("B", 69);
899efcde27bf74acdd82bb0aad0ad1fd0b767ca5f18Andy Stadler        cv.put("C", 0);
900efcde27bf74acdd82bb0aad0ad1fd0b767ca5f18Andy Stadler        assertTrue(CalendarUtilities.getIntegerValueAsBoolean(cv, "A"));
901efcde27bf74acdd82bb0aad0ad1fd0b767ca5f18Andy Stadler        assertTrue(CalendarUtilities.getIntegerValueAsBoolean(cv, "B"));
902efcde27bf74acdd82bb0aad0ad1fd0b767ca5f18Andy Stadler        assertFalse(CalendarUtilities.getIntegerValueAsBoolean(cv, "C"));
903efcde27bf74acdd82bb0aad0ad1fd0b767ca5f18Andy Stadler        assertFalse(CalendarUtilities.getIntegerValueAsBoolean(cv, "D"));
904efcde27bf74acdd82bb0aad0ad1fd0b767ca5f18Andy Stadler    }
905f3fcb8929e3690bc092aec0d4d643b346f079acdMarc Blank}
906802a7d69b54b3d560ea12255d44503dda54f1dbdMarc Blank
907802a7d69b54b3d560ea12255d44503dda54f1dbdMarc Blank    // TODO Planned unit tests
908802a7d69b54b3d560ea12255d44503dda54f1dbdMarc Blank    // findNextTransition
909802a7d69b54b3d560ea12255d44503dda54f1dbdMarc Blank    // recurrenceFromRrule
910802a7d69b54b3d560ea12255d44503dda54f1dbdMarc Blank    // timeZoneToTziStringImpl
911802a7d69b54b3d560ea12255d44503dda54f1dbdMarc Blank    // getDSTCalendars
912802a7d69b54b3d560ea12255d44503dda54f1dbdMarc Blank    // millisToVCalendarTime
913802a7d69b54b3d560ea12255d44503dda54f1dbdMarc Blank    // millisToEasDateTime
914802a7d69b54b3d560ea12255d44503dda54f1dbdMarc Blank    // getTrueTransitionMinute
915802a7d69b54b3d560ea12255d44503dda54f1dbdMarc Blank    // getTrueTransitionHour
916802a7d69b54b3d560ea12255d44503dda54f1dbdMarc Blank
917