1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.exchange.utility; 18 19 import android.content.ContentValues; 20 import android.content.Entity; 21 import android.content.res.Resources; 22 import android.provider.CalendarContract.Attendees; 23 import android.provider.CalendarContract.Events; 24 import android.test.suitebuilder.annotation.MediumTest; 25 import android.test.AndroidTestCase; 26 27 import com.android.emailcommon.mail.Address; 28 import com.android.emailcommon.provider.Account; 29 import com.android.emailcommon.provider.EmailContent.Attachment; 30 import com.android.emailcommon.provider.EmailContent.Message; 31 import com.android.emailcommon.utility.Utility; 32 import com.android.exchange.R; 33 import com.android.exchange.adapter.CalendarSyncParser; 34 import com.android.exchange.adapter.Parser; 35 import com.android.exchange.adapter.Serializer; 36 import com.android.exchange.adapter.SyncAdapterTestCase; 37 import com.android.exchange.adapter.Tags; 38 import com.android.mail.utils.LogUtils; 39 40 import java.io.BufferedReader; 41 import java.io.ByteArrayInputStream; 42 import java.io.IOException; 43 import java.io.StringReader; 44 import java.text.DateFormat; 45 import java.util.ArrayList; 46 import java.util.Calendar; 47 import java.util.Date; 48 import java.util.GregorianCalendar; 49 import java.util.HashMap; 50 import java.util.TimeZone; 51 52 /** 53 * Tests of EAS Calendar Utilities 54 * You can run this entire test case with: 55 * runtest -c com.android.exchange.utility.CalendarUtilitiesTests exchange 56 * 57 * Please see RFC2445 for RRULE definition 58 * http://www.ietf.org/rfc/rfc2445.txt 59 */ 60 @MediumTest 61 public class CalendarUtilitiesTests extends AndroidTestCase { 62 63 // Some prebuilt time zones, Base64 encoded (as they arrive from EAS) 64 // More time zones to be added over time 65 66 // Not all time zones are appropriate for testing. For example, ISRAEL_STANDARD_TIME cannot be 67 // used because DST is determined from year to year in a non-standard way (related to the lunar 68 // calendar); therefore, the test would only work during the year in which it was created 69 70 // This time zone has no DST 71 private static final String ASIA_CALCUTTA_TIME = 72 "tv7//0kAbgBkAGkAYQAgAFMAdABhAG4AZABhAHIAZAAgAFQAaQBtAGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + 73 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEkAbgBkAGkAYQAgAEQAYQB5AGwAaQBnAGgAdAAgAFQAaQBtAGUA" + 74 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="; 75 76 // This time zone is equivalent to PST and uses DST 77 private static final String AMERICA_DAWSON_TIME = 78 "4AEAAFAAYQBjAGkAZgBpAGMAIABTAHQAYQBuAGQAYQByAGQAIABUAGkAbQBlAAAAAAAAAAAAAAAAAAAAAAAA" + 79 "AAAAAAAAAAsAAAABAAIAAAAAAAAAAAAAAFAAYQBjAGkAZgBpAGMAIABEAGEAeQBsAGkAZwBoAHQAIABUAGkA" + 80 "bQBlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAACAAIAAAAAAAAAxP///w=="; 81 82 // Test a southern hemisphere time zone w/ DST 83 private static final String AUSTRALIA_ACT_TIME = 84 "qP3//0EAVQBTACAARQBhAHMAdABlAHIAbgAgAFMAdABhAG4AZABhAHIAZAAgAFQAaQBtAGUAAAAAAAAAAAAA" + 85 "AAAAAAAAAAQAAAABAAMAAAAAAAAAAAAAAEEAVQBTACAARQBhAHMAdABlAHIAbgAgAEQAYQB5AGwAaQBnAGgA" + 86 "dAAgAFQAaQBtAGUAAAAAAAAAAAAAAAAAAAAAAAoAAAABAAIAAAAAAAAAxP///w=="; 87 88 // Test a timezone with GMT bias but bogus DST parameters (there is no equivalent time zone 89 // in the database) 90 private static final String GMT_UNKNOWN_DAYLIGHT_TIME = 91 "AAAAACgARwBNAFQAKwAwADAAOgAwADAAKQAgAFQAaQBtAGUAIABaAG8AbgBlAAAAAAAAAAAAAAAAAAAAAAAA" + 92 "AAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAACgARwBNAFQAKwAwADAAOgAwADAAKQAgAFQAaQBtAGUAIABaAG8A" + 93 "bgBlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAFAAEAAAAAAAAAxP///w=="; 94 95 // This time zone has no DST, but earlier, buggy code retrieved a TZ WITH DST 96 private static final String ARIZONA_TIME = 97 "pAEAAFUAUwAgAE0AbwB1AG4AdABhAGkAbgAgAFMAdABhAG4AZABhAHIAZAAgAFQAaQBtAGUAAAAAAAAAAAAA" + 98 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFUAUwAgAE0AbwB1AG4AdABhAGkAbgAgAEQAYQB5AGwAaQBnAGgA" + 99 "dAAgAFQAaQBtAGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="; 100 101 private static final String HAWAII_TIME = 102 "WAIAAEgAYQB3AGEAaQBpAGEAbgAgAFMAdABhAG4AZABhAHIAZAAgAFQAaQBtAGUAAAAAAAAAAAAAAAAAAAAA" + 103 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEgAYQB3AGEAaQBpAGEAbgAgAEQAYQB5AGwAaQBnAGgAdAAgAFQA" + 104 "aQBtAGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="; 105 106 // This is time zone sent by Exchange 2007, apparently; the start time of DST for the eastern 107 // time zone (EST) is off by two hours, which we should correct in our new "lenient" code 108 private static final String LENIENT_EASTERN_TIME = 109 "LAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + 110 "AAAAAAAAAAsAAAABAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + 111 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAACAAAAAAAAAAAAxP///w=="; 112 113 // This string specifies "Europe/London" in the name, but otherwise is somewhat bogus 114 // in that it has unknown time zone dates with a 0 bias (GMT). (From a Zimbra server user) 115 private static final String EUROPE_LONDON_TIME_BY_NAME = 116 "AAAAAEV1cm9wZS9Mb25kb24AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + 117 "AAAAAAAAAAoAAQAFAAIAAAAAAAAAAAAAAEV1cm9wZS9Mb25kb24AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + 118 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAQAFAAEAAAAAAAAAxP///w=="; 119 120 private static final String ORGANIZER = "organizer (at) server.com"; 121 private static final String ATTENDEE = "attendee (at) server.com"; 122 123 public void testGetSet() { 124 byte[] bytes = new byte[] {0, 1, 2, 3, 4, 5, 6, 7}; 125 126 // First, check that getWord/Long are properly little endian 127 assertEquals(0x0100, CalendarUtilities.getWord(bytes, 0)); 128 assertEquals(0x03020100, CalendarUtilities.getLong(bytes, 0)); 129 assertEquals(0x07060504, CalendarUtilities.getLong(bytes, 4)); 130 131 // Set some words and longs 132 CalendarUtilities.setWord(bytes, 0, 0xDEAD); 133 CalendarUtilities.setLong(bytes, 2, 0xBEEFBEEF); 134 CalendarUtilities.setWord(bytes, 6, 0xCEDE); 135 136 // Retrieve them 137 assertEquals(0xDEAD, CalendarUtilities.getWord(bytes, 0)); 138 assertEquals(0xBEEFBEEF, CalendarUtilities.getLong(bytes, 2)); 139 assertEquals(0xCEDE, CalendarUtilities.getWord(bytes, 6)); 140 } 141 142 public void testParseTimeZoneEndToEnd() { 143 TimeZone tz = CalendarUtilities.tziStringToTimeZone(AMERICA_DAWSON_TIME); 144 assertEquals("America/Dawson", tz.getID()); 145 tz = CalendarUtilities.tziStringToTimeZone(ASIA_CALCUTTA_TIME); 146 assertEquals("Asia/Calcutta", tz.getID()); 147 tz = CalendarUtilities.tziStringToTimeZone(AUSTRALIA_ACT_TIME); 148 assertEquals("Australia/ACT", tz.getID()); 149 150 tz = CalendarUtilities.tziStringToTimeZone(EUROPE_LONDON_TIME_BY_NAME); 151 assertEquals("Europe/London", tz.getID()); 152 153 // Test peculiar MS sent EST data with and without lenient precision; send standard 154 // precision + 1 (i.e. 1ms) to make sure the code doesn't automatically flip to lenient 155 // when the tz isn't found 156 tz = CalendarUtilities.tziStringToTimeZoneImpl(LENIENT_EASTERN_TIME, 157 CalendarUtilities.STANDARD_DST_PRECISION+1); 158 assertEquals("America/Atikokan", tz.getID()); 159 tz = CalendarUtilities.tziStringToTimeZoneImpl(LENIENT_EASTERN_TIME, 160 CalendarUtilities.LENIENT_DST_PRECISION); 161 assertEquals("America/Detroit", tz.getID()); 162 163 tz = CalendarUtilities.tziStringToTimeZone(GMT_UNKNOWN_DAYLIGHT_TIME); 164 int bias = tz.getOffset(System.currentTimeMillis()); 165 assertEquals(0, bias); 166 // Make sure non-DST TZ's work properly when the tested zone is the default zone 167 TimeZone.setDefault(TimeZone.getTimeZone("America/Phoenix")); 168 tz = CalendarUtilities.tziStringToTimeZone(ARIZONA_TIME); 169 assertEquals("America/Phoenix", tz.getID()); 170 TimeZone.setDefault(TimeZone.getTimeZone("Pacific/Honolulu")); 171 tz = CalendarUtilities.tziStringToTimeZone(HAWAII_TIME); 172 assertEquals("Pacific/Honolulu", tz.getID()); 173 // Make sure non-DST TZ's get the proper offset and DST status otherwise 174 CalendarUtilities.clearTimeZoneCache(); 175 TimeZone azTime = TimeZone.getTimeZone("America/Phoenix"); 176 TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles")); 177 tz = CalendarUtilities.tziStringToTimeZone(ARIZONA_TIME); 178 assertFalse("America/Phoenix".equals(tz.getID())); 179 assertFalse(tz.useDaylightTime()); 180 // It doesn't matter what time is passed in, since neither TZ has dst 181 long now = System.currentTimeMillis(); 182 assertEquals(azTime.getOffset(now), tz.getOffset(now)); 183 } 184 185 public void testGenerateEasDayOfWeek() { 186 String byDay = "TU,WE,SA"; 187 // TU = 4, WE = 8; SA = 64; 188 assertEquals("76", CalendarUtilities.generateEasDayOfWeek(byDay)); 189 // MO = 2, TU = 4; WE = 8; TH = 16; FR = 32 190 byDay = "MO,TU,WE,TH,FR"; 191 assertEquals("62", CalendarUtilities.generateEasDayOfWeek(byDay)); 192 // SU = 1 193 byDay = "SU"; 194 assertEquals("1", CalendarUtilities.generateEasDayOfWeek(byDay)); 195 } 196 197 public void testTokenFromRrule() { 198 String rrule = "FREQ=DAILY;INTERVAL=1;BYDAY=WE,TH,SA;BYMONTHDAY=17"; 199 assertEquals("DAILY", CalendarUtilities.tokenFromRrule(rrule, "FREQ=")); 200 assertEquals("1", CalendarUtilities.tokenFromRrule(rrule, "INTERVAL=")); 201 assertEquals("17", CalendarUtilities.tokenFromRrule(rrule, "BYMONTHDAY=")); 202 assertEquals("WE,TH,SA", CalendarUtilities.tokenFromRrule(rrule, "BYDAY=")); 203 assertNull(CalendarUtilities.tokenFromRrule(rrule, "UNTIL=")); 204 } 205 206 public void testRecurrenceUntilToEasUntil() { 207 TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles")); 208 // Case where local time crosses into next day in GMT 209 assertEquals("20110730T000000Z", 210 CalendarUtilities.recurrenceUntilToEasUntil("20110731T025959Z")); 211 // Case where local time does not cross into next day in GMT 212 assertEquals("20110730T000000Z", 213 CalendarUtilities.recurrenceUntilToEasUntil("20110730T235959Z")); 214 } 215 216 public void testParseEmailDateTimeToMillis(String date) { 217 // Format for email date strings is 2010-02-23T16:00:00.000Z 218 String dateString = "2010-02-23T15:16:17.000Z"; 219 long dateTime = Utility.parseEmailDateTimeToMillis(dateString); 220 GregorianCalendar cal = new GregorianCalendar(); 221 cal.setTimeInMillis(dateTime); 222 cal.setTimeZone(TimeZone.getTimeZone("GMT")); 223 assertEquals(cal.get(Calendar.YEAR), 2010); 224 assertEquals(cal.get(Calendar.MONTH), 1); // 0 based 225 assertEquals(cal.get(Calendar.DAY_OF_MONTH), 23); 226 assertEquals(cal.get(Calendar.HOUR_OF_DAY), 16); 227 assertEquals(cal.get(Calendar.MINUTE), 16); 228 assertEquals(cal.get(Calendar.SECOND), 17); 229 } 230 231 public void testParseDateTimeToMillis(String date) { 232 // Format for calendar date strings is 20100223T160000000Z 233 String dateString = "20100223T151617000Z"; 234 long dateTime = Utility.parseDateTimeToMillis(dateString); 235 GregorianCalendar cal = new GregorianCalendar(); 236 cal.setTimeInMillis(dateTime); 237 cal.setTimeZone(TimeZone.getTimeZone("GMT")); 238 assertEquals(cal.get(Calendar.YEAR), 2010); 239 assertEquals(cal.get(Calendar.MONTH), 1); // 0 based 240 assertEquals(cal.get(Calendar.DAY_OF_MONTH), 23); 241 assertEquals(cal.get(Calendar.HOUR_OF_DAY), 16); 242 assertEquals(cal.get(Calendar.MINUTE), 16); 243 assertEquals(cal.get(Calendar.SECOND), 17); 244 } 245 246 private Entity setupTestEventEntity(String organizer, String attendee, String title) { 247 // Create an Entity for an Event 248 ContentValues entityValues = new ContentValues(); 249 Entity entity = new Entity(entityValues); 250 251 // Set up values for the Event 252 String location = "Meeting Location"; 253 254 // Fill in times, location, title, and organizer 255 entityValues.put("DTSTAMP", 256 CalendarUtilities.convertEmailDateTimeToCalendarDateTime("2010-04-05T14:30:51Z")); 257 entityValues.put(Events.DTSTART, 258 Utility.parseEmailDateTimeToMillis("2010-04-12T18:30:00Z")); 259 entityValues.put(Events.DTEND, 260 Utility.parseEmailDateTimeToMillis("2010-04-12T19:30:00Z")); 261 entityValues.put(Events.EVENT_LOCATION, location); 262 entityValues.put(Events.TITLE, title); 263 entityValues.put(Events.ORGANIZER, organizer); 264 entityValues.put(Events.SYNC_DATA2, "31415926535"); 265 266 // Add the attendee 267 ContentValues attendeeValues = new ContentValues(); 268 attendeeValues.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE); 269 attendeeValues.put(Attendees.ATTENDEE_EMAIL, attendee); 270 entity.addSubValue(Attendees.CONTENT_URI, attendeeValues); 271 272 // Add the organizer 273 ContentValues organizerValues = new ContentValues(); 274 organizerValues.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ORGANIZER); 275 organizerValues.put(Attendees.ATTENDEE_EMAIL, organizer); 276 entity.addSubValue(Attendees.CONTENT_URI, organizerValues); 277 return entity; 278 } 279 280 private Entity setupTestExceptionEntity(String organizer, String attendee, String title) { 281 Entity entity = setupTestEventEntity(organizer, attendee, title); 282 ContentValues entityValues = entity.getEntityValues(); 283 entityValues.put(Events.ORIGINAL_SYNC_ID, 69); 284 // The exception will be on April 26th 285 entityValues.put(Events.ORIGINAL_INSTANCE_TIME, 286 Utility.parseEmailDateTimeToMillis("2010-04-26T18:30:00Z")); 287 return entity; 288 } 289 290 public void testCreateMessageForEntity_Reply() { 291 // Set up the "event" 292 String title = "Discuss Unit Tests"; 293 Entity entity = setupTestEventEntity(ORGANIZER, ATTENDEE, title); 294 295 // Create a dummy account for the attendee 296 Account account = new Account(); 297 account.mEmailAddress = ATTENDEE; 298 299 // The uid is required, but can be anything 300 String uid = "31415926535"; 301 302 // Create the outgoing message 303 Message msg = CalendarUtilities.createMessageForEntity(mContext, entity, 304 Message.FLAG_OUTGOING_MEETING_ACCEPT, uid, account); 305 306 // First, we should have a message 307 assertNotNull(msg); 308 309 // Now check some of the fields of the message 310 assertEquals(Address.pack(new Address[] {new Address(ORGANIZER)}), msg.mTo); 311 Resources resources = getContext().getResources(); 312 String accept = resources.getString(R.string.meeting_accepted, title); 313 assertEquals(accept, msg.mSubject); 314 assertNotNull(msg.mText); 315 assertTrue(msg.mText.contains(resources.getString(R.string.meeting_where, ""))); 316 317 // And make sure we have an attachment 318 assertNotNull(msg.mAttachments); 319 assertEquals(1, msg.mAttachments.size()); 320 Attachment att = msg.mAttachments.get(0); 321 // And that the attachment has the correct elements 322 assertEquals("invite.ics", att.mFileName); 323 assertEquals(Attachment.FLAG_ICS_ALTERNATIVE_PART, 324 att.mFlags & Attachment.FLAG_ICS_ALTERNATIVE_PART); 325 assertEquals("text/calendar; method=REPLY", att.mMimeType); 326 assertNotNull(att.mContentBytes); 327 assertEquals(att.mSize, att.mContentBytes.length); 328 329 //TODO Check the contents of the attachment using an iCalendar parser 330 } 331 332 public void testCreateMessageForEntity_Invite_AllDay() throws IOException { 333 // Set up the "event" 334 String title = "Discuss Unit Tests"; 335 Entity entity = setupTestEventEntity(ORGANIZER, ATTENDEE, title); 336 ContentValues entityValues = entity.getEntityValues(); 337 entityValues.put(Events.ALL_DAY, 1); 338 entityValues.put(Events.DURATION, "P1D"); 339 entityValues.remove(Events.DTEND); 340 341 // Create a dummy account for the attendee 342 Account account = new Account(); 343 account.mEmailAddress = ORGANIZER; 344 345 // The uid is required, but can be anything 346 String uid = "31415926535"; 347 348 // Create the outgoing message 349 Message msg = CalendarUtilities.createMessageForEntity(mContext, entity, 350 Message.FLAG_OUTGOING_MEETING_INVITE, uid, account); 351 352 // First, we should have a message 353 assertNotNull(msg); 354 355 // Now check some of the fields of the message 356 assertEquals(Address.pack(new Address[] {new Address(ATTENDEE)}), msg.mTo); 357 assertEquals(title, msg.mSubject); 358 359 // And make sure we have an attachment 360 assertNotNull(msg.mAttachments); 361 assertEquals(1, msg.mAttachments.size()); 362 Attachment att = msg.mAttachments.get(0); 363 // And that the attachment has the correct elements 364 assertEquals("invite.ics", att.mFileName); 365 assertEquals(Attachment.FLAG_ICS_ALTERNATIVE_PART, 366 att.mFlags & Attachment.FLAG_ICS_ALTERNATIVE_PART); 367 assertEquals("text/calendar; method=REQUEST", att.mMimeType); 368 assertNotNull(att.mContentBytes); 369 assertEquals(att.mSize, att.mContentBytes.length); 370 371 // We'll check the contents of the ics file here 372 BlockHash vcalendar = parseIcsContent(att.mContentBytes); 373 assertNotNull(vcalendar); 374 375 // We should have a VCALENDAR with a REQUEST method 376 assertEquals("VCALENDAR", vcalendar.name); 377 assertEquals("REQUEST", vcalendar.get("METHOD")); 378 379 // We should have one block under VCALENDAR 380 assertEquals(1, vcalendar.blocks.size()); 381 BlockHash vevent = vcalendar.blocks.get(0); 382 // It's a VEVENT with the following fields 383 assertEquals("VEVENT", vevent.name); 384 assertEquals("Meeting Location", vevent.get("LOCATION")); 385 assertEquals("0", vevent.get("SEQUENCE")); 386 assertEquals("Discuss Unit Tests", vevent.get("SUMMARY")); 387 assertEquals(uid, vevent.get("UID")); 388 assertEquals("MAILTO:" + ATTENDEE, 389 vevent.get("ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE")); 390 391 // These next two fields should have a date only 392 assertEquals("20100412", vevent.get("DTSTART;VALUE=DATE")); 393 assertEquals("20100413", vevent.get("DTEND;VALUE=DATE")); 394 // This should be set to TRUE for all-day events 395 assertEquals("TRUE", vevent.get("X-MICROSOFT-CDO-ALLDAYEVENT")); 396 } 397 398 public void testCreateMessageForEntity_Invite() throws IOException { 399 // Set up the "event" 400 String title = "Discuss Unit Tests"; 401 Entity entity = setupTestEventEntity(ORGANIZER, ATTENDEE, title); 402 403 // Create a dummy account for the attendee 404 Account account = new Account(); 405 account.mEmailAddress = ORGANIZER; 406 407 // The uid is required, but can be anything 408 String uid = "31415926535"; 409 410 // Create the outgoing message 411 Message msg = CalendarUtilities.createMessageForEntity(mContext, entity, 412 Message.FLAG_OUTGOING_MEETING_INVITE, uid, account); 413 414 // First, we should have a message 415 assertNotNull(msg); 416 417 // Now check some of the fields of the message 418 assertEquals(Address.pack(new Address[] {new Address(ATTENDEE)}), msg.mTo); 419 assertEquals(title, msg.mSubject); 420 421 // And make sure we have an attachment 422 assertNotNull(msg.mAttachments); 423 assertEquals(1, msg.mAttachments.size()); 424 Attachment att = msg.mAttachments.get(0); 425 // And that the attachment has the correct elements 426 assertEquals("invite.ics", att.mFileName); 427 assertEquals(Attachment.FLAG_ICS_ALTERNATIVE_PART, 428 att.mFlags & Attachment.FLAG_ICS_ALTERNATIVE_PART); 429 assertEquals("text/calendar; method=REQUEST", att.mMimeType); 430 assertNotNull(att.mContentBytes); 431 assertEquals(att.mSize, att.mContentBytes.length); 432 433 // We'll check the contents of the ics file here 434 BlockHash vcalendar = parseIcsContent(att.mContentBytes); 435 assertNotNull(vcalendar); 436 437 // We should have a VCALENDAR with a REQUEST method 438 assertEquals("VCALENDAR", vcalendar.name); 439 assertEquals("REQUEST", vcalendar.get("METHOD")); 440 441 // We should have one block under VCALENDAR 442 assertEquals(1, vcalendar.blocks.size()); 443 BlockHash vevent = vcalendar.blocks.get(0); 444 // It's a VEVENT with the following fields 445 assertEquals("VEVENT", vevent.name); 446 assertEquals("Meeting Location", vevent.get("LOCATION")); 447 assertEquals("0", vevent.get("SEQUENCE")); 448 assertEquals("Discuss Unit Tests", vevent.get("SUMMARY")); 449 assertEquals(uid, vevent.get("UID")); 450 assertEquals("MAILTO:" + ATTENDEE, 451 vevent.get("ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE")); 452 453 // These next two fields should exist (without the VALUE=DATE suffix) 454 assertNotNull(vevent.get("DTSTART")); 455 assertNotNull(vevent.get("DTEND")); 456 assertNull(vevent.get("DTSTART;VALUE=DATE")); 457 assertNull(vevent.get("DTEND;VALUE=DATE")); 458 // This shouldn't exist for this event 459 assertNull(vevent.get("X-MICROSOFT-CDO-ALLDAYEVENT")); 460 } 461 462 public void testCreateMessageForEntity_Recurring() throws IOException { 463 // Set up the "event" 464 String title = "Discuss Unit Tests"; 465 Entity entity = setupTestEventEntity(ORGANIZER, ATTENDEE, title); 466 // Set up a RRULE for this event 467 entity.getEntityValues().put(Events.RRULE, "FREQ=DAILY"); 468 469 // Create a dummy account for the attendee 470 Account account = new Account(); 471 account.mEmailAddress = ORGANIZER; 472 473 // The uid is required, but can be anything 474 String uid = "31415926535"; 475 476 // Create the outgoing message 477 Message msg = CalendarUtilities.createMessageForEntity(mContext, entity, 478 Message.FLAG_OUTGOING_MEETING_INVITE, uid, account); 479 480 // First, we should have a message 481 assertNotNull(msg); 482 483 // Now check some of the fields of the message 484 assertEquals(Address.pack(new Address[] {new Address(ATTENDEE)}), msg.mTo); 485 assertEquals(title, msg.mSubject); 486 487 // And make sure we have an attachment 488 assertNotNull(msg.mAttachments); 489 assertEquals(1, msg.mAttachments.size()); 490 Attachment att = msg.mAttachments.get(0); 491 // And that the attachment has the correct elements 492 assertEquals("invite.ics", att.mFileName); 493 assertEquals(Attachment.FLAG_ICS_ALTERNATIVE_PART, 494 att.mFlags & Attachment.FLAG_ICS_ALTERNATIVE_PART); 495 assertEquals("text/calendar; method=REQUEST", att.mMimeType); 496 assertNotNull(att.mContentBytes); 497 assertEquals(att.mSize, att.mContentBytes.length); 498 499 // We'll check the contents of the ics file here 500 BlockHash vcalendar = parseIcsContent(att.mContentBytes); 501 assertNotNull(vcalendar); 502 503 // We should have a VCALENDAR with a REQUEST method 504 assertEquals("VCALENDAR", vcalendar.name); 505 assertEquals("REQUEST", vcalendar.get("METHOD")); 506 507 // We should have two blocks under VCALENDAR (VTIMEZONE and VEVENT) 508 assertEquals(2, vcalendar.blocks.size()); 509 510 // This is the time zone that should be used 511 TimeZone timeZone = TimeZone.getDefault(); 512 513 BlockHash vtimezone = vcalendar.blocks.get(0); 514 // It should be a VTIMEZONE for timeZone 515 assertEquals("VTIMEZONE", vtimezone.name); 516 assertEquals(timeZone.getID(), vtimezone.get("TZID")); 517 518 BlockHash vevent = vcalendar.blocks.get(1); 519 // It's a VEVENT with the following fields 520 assertEquals("VEVENT", vevent.name); 521 assertEquals("Meeting Location", vevent.get("LOCATION")); 522 assertEquals("0", vevent.get("SEQUENCE")); 523 assertEquals("Discuss Unit Tests", vevent.get("SUMMARY")); 524 assertEquals(uid, vevent.get("UID")); 525 assertEquals("MAILTO:" + ATTENDEE, 526 vevent.get("ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE")); 527 528 // We should have DTSTART/DTEND with time zone 529 assertNotNull(vevent.get("DTSTART;TZID=" + timeZone.getID())); 530 assertNotNull(vevent.get("DTEND;TZID=" + timeZone.getID())); 531 assertNull(vevent.get("DTSTART")); 532 assertNull(vevent.get("DTEND")); 533 assertNull(vevent.get("DTSTART;VALUE=DATE")); 534 assertNull(vevent.get("DTEND;VALUE=DATE")); 535 // This shouldn't exist for this event 536 assertNull(vevent.get("X-MICROSOFT-CDO-ALLDAYEVENT")); 537 } 538 539 public void testCreateMessageForEntity_Exception_Cancel() throws IOException { 540 // Set up the "exception"... 541 String title = "Discuss Unit Tests"; 542 Entity entity = setupTestExceptionEntity(ORGANIZER, ATTENDEE, title); 543 544 ContentValues entityValues = entity.getEntityValues(); 545 // Mark the Exception as dirty 546 entityValues.put(Events.DIRTY, 1); 547 // And mark it canceled 548 entityValues.put(Events.STATUS, Events.STATUS_CANCELED); 549 550 // Create a dummy account for the attendee 551 Account account = new Account(); 552 account.mEmailAddress = ORGANIZER; 553 554 // The uid is required, but can be anything 555 String uid = "31415926535"; 556 557 // Create the outgoing message 558 Message msg = CalendarUtilities.createMessageForEntity(mContext, entity, 559 Message.FLAG_OUTGOING_MEETING_CANCEL, uid, account); 560 561 // First, we should have a message 562 assertNotNull(msg); 563 564 // Now check some of the fields of the message 565 assertEquals(Address.pack(new Address[] {new Address(ATTENDEE)}), msg.mTo); 566 String cancel = getContext().getResources().getString(R.string.meeting_canceled, title); 567 assertEquals(cancel, msg.mSubject); 568 569 // And make sure we have an attachment 570 assertNotNull(msg.mAttachments); 571 assertEquals(1, msg.mAttachments.size()); 572 Attachment att = msg.mAttachments.get(0); 573 // And that the attachment has the correct elements 574 assertEquals("invite.ics", att.mFileName); 575 assertEquals(Attachment.FLAG_ICS_ALTERNATIVE_PART, 576 att.mFlags & Attachment.FLAG_ICS_ALTERNATIVE_PART); 577 assertEquals("text/calendar; method=CANCEL", att.mMimeType); 578 assertNotNull(att.mContentBytes); 579 580 // We'll check the contents of the ics file here 581 BlockHash vcalendar = parseIcsContent(att.mContentBytes); 582 assertNotNull(vcalendar); 583 584 // We should have a VCALENDAR with a CANCEL method 585 assertEquals("VCALENDAR", vcalendar.name); 586 assertEquals("CANCEL", vcalendar.get("METHOD")); 587 588 // This is the time zone that should be used 589 TimeZone timeZone = TimeZone.getDefault(); 590 591 // We should have two blocks under VCALENDAR (VTIMEZONE and VEVENT) 592 assertEquals(2, vcalendar.blocks.size()); 593 594 BlockHash vtimezone = vcalendar.blocks.get(0); 595 // It should be a VTIMEZONE for timeZone 596 assertEquals("VTIMEZONE", vtimezone.name); 597 assertEquals(timeZone.getID(), vtimezone.get("TZID")); 598 599 BlockHash vevent = vcalendar.blocks.get(1); 600 // It's a VEVENT with the following fields 601 assertEquals("VEVENT", vevent.name); 602 assertEquals("Meeting Location", vevent.get("LOCATION")); 603 assertEquals("0", vevent.get("SEQUENCE")); 604 assertEquals("Discuss Unit Tests", vevent.get("SUMMARY")); 605 assertEquals(uid, vevent.get("UID")); 606 assertEquals("MAILTO:" + ATTENDEE, 607 vevent.get("ATTENDEE;ROLE=REQ-PARTICIPANT")); 608 long originalTime = entityValues.getAsLong(Events.ORIGINAL_INSTANCE_TIME); 609 assertNotSame(0, originalTime); 610 // For an exception, RECURRENCE-ID is critical 611 assertEquals(CalendarUtilities.millisToEasDateTime(originalTime, timeZone, 612 true /*withTime*/), vevent.get("RECURRENCE-ID" + ";TZID=" + timeZone.getID())); 613 } 614 615 public void testUtcOffsetString() { 616 assertEquals(CalendarUtilities.utcOffsetString(540), "+0900"); 617 assertEquals(CalendarUtilities.utcOffsetString(-480), "-0800"); 618 assertEquals(CalendarUtilities.utcOffsetString(0), "+0000"); 619 } 620 621 public void testFindTransitionDate() { 622 // We'll find some transitions and make sure that we're properly in or out of daylight time 623 // on either side of the transition. 624 // Use CST for testing (any other will do as well, as long as it has DST) 625 TimeZone tz = TimeZone.getTimeZone("US/Central"); 626 // Confirm that this time zone uses DST 627 assertTrue(tz.useDaylightTime()); 628 // Get a calendar at January 1st of the current year 629 GregorianCalendar calendar = new GregorianCalendar(tz); 630 calendar.set(CalendarUtilities.sCurrentYear, Calendar.JANUARY, 1); 631 // Get start and end times at start and end of year 632 long startTime = calendar.getTimeInMillis(); 633 long endTime = startTime + (365*CalendarUtilities.DAYS); 634 // Find the first transition 635 GregorianCalendar transitionCalendar = 636 CalendarUtilities.findTransitionDate(tz, startTime, endTime, false); 637 long transitionTime = transitionCalendar.getTimeInMillis(); 638 // Before should be in standard time; after in daylight time 639 Date beforeDate = new Date(transitionTime - CalendarUtilities.HOURS); 640 Date afterDate = new Date(transitionTime + CalendarUtilities.HOURS); 641 assertFalse(tz.inDaylightTime(beforeDate)); 642 assertTrue(tz.inDaylightTime(afterDate)); 643 644 // Find the next one... 645 transitionCalendar = CalendarUtilities.findTransitionDate(tz, transitionTime + 646 CalendarUtilities.DAYS, endTime, true); 647 transitionTime = transitionCalendar.getTimeInMillis(); 648 // This time, Before should be in daylight time; after in standard time 649 beforeDate = new Date(transitionTime - CalendarUtilities.HOURS); 650 afterDate = new Date(transitionTime + CalendarUtilities.HOURS); 651 assertTrue(tz.inDaylightTime(beforeDate)); 652 assertFalse(tz.inDaylightTime(afterDate)); 653 654 // Kinshasa has no daylight savings time 655 tz = TimeZone.getTimeZone("Africa/Kinshasa"); 656 // Confirm that there's no DST for this time zone 657 assertFalse(tz.useDaylightTime()); 658 // Get a calendar at January 1st of the current year 659 calendar = new GregorianCalendar(tz); 660 calendar.set(CalendarUtilities.sCurrentYear, Calendar.JANUARY, 1); 661 // Get start and end times at start and end of year 662 startTime = calendar.getTimeInMillis(); 663 endTime = startTime + (365*CalendarUtilities.DAYS); 664 // Find the first transition 665 transitionCalendar = CalendarUtilities.findTransitionDate(tz, startTime, endTime, false); 666 // There had better not be one 667 assertNull(transitionCalendar); 668 } 669 670 public void testRruleFromRecurrence() { 671 // Every Monday for 2 weeks 672 String rrule = CalendarUtilities.rruleFromRecurrence( 673 1 /*Weekly*/, 2 /*Occurrences*/, 1 /*Interval*/, 2 /*Monday*/, 0, 0, 0, null); 674 assertEquals("FREQ=WEEKLY;COUNT=2;INTERVAL=1;BYDAY=MO", rrule); 675 // Every Tuesday and Friday 676 rrule = CalendarUtilities.rruleFromRecurrence( 677 1 /*Weekly*/, 0 /*Occurrences*/, 0 /*Interval*/, 36 /*Tue&Fri*/, 0, 0, 0, null); 678 assertEquals("FREQ=WEEKLY;BYDAY=TU,FR", rrule); 679 // The last Saturday of the month 680 rrule = CalendarUtilities.rruleFromRecurrence( 681 1 /*Weekly*/, 0, 0, 64 /*Sat*/, 0, 5 /*Last*/, 0, null); 682 assertEquals("FREQ=WEEKLY;BYDAY=-1SA", rrule); 683 // The third Wednesday and Thursday of the month 684 rrule = CalendarUtilities.rruleFromRecurrence( 685 1 /*Weekly*/, 0, 0, 24 /*Wed&Thu*/, 0, 3 /*3rd*/, 0, null); 686 assertEquals("FREQ=WEEKLY;BYDAY=3WE,3TH", rrule); 687 rrule = CalendarUtilities.rruleFromRecurrence( 688 3 /*Monthly/Day*/, 0, 0, 127 /*LastDay*/, 0, 0, 0, null); 689 assertEquals("FREQ=MONTHLY;BYMONTHDAY=-1", rrule); 690 rrule = CalendarUtilities.rruleFromRecurrence( 691 3 /*Monthly/Day*/, 0, 0, 62 /*M-F*/, 0, 5 /*Last week*/, 0, null); 692 assertEquals("FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1", rrule); 693 // The 14th of the every month 694 rrule = CalendarUtilities.rruleFromRecurrence( 695 2 /*Monthly/Date*/, 0, 0, 0, 14 /*14th*/, 0, 0, null); 696 assertEquals("FREQ=MONTHLY;BYMONTHDAY=14", rrule); 697 // Every 31st of October 698 rrule = CalendarUtilities.rruleFromRecurrence( 699 5 /*Yearly/Date*/, 0, 0, 0, 31 /*31st*/, 0, 10 /*October*/, null); 700 assertEquals("FREQ=YEARLY;BYMONTHDAY=31;BYMONTH=10", rrule); 701 // The first Tuesday of June 702 rrule = CalendarUtilities.rruleFromRecurrence( 703 6 /*Yearly/Month/DayOfWeek*/, 0, 0, 4 /*Tue*/, 0, 1 /*1st*/, 6 /*June*/, null); 704 assertEquals("FREQ=YEARLY;BYDAY=1TU;BYMONTH=6", rrule); 705 } 706 707 /** 708 * For debugging purposes, to help keep track of parsing errors. 709 */ 710 private class UnterminatedBlockException extends IOException { 711 private static final long serialVersionUID = 1L; 712 UnterminatedBlockException(String name) { 713 super(name); 714 } 715 } 716 717 /** 718 * A lightweight representation of block object containing a hash of individual values and an 719 * array of inner blocks. The object is build by pulling elements from a BufferedReader. 720 * NOTE: Multiple values of a given field are not supported. We'd see this with ATTENDEEs, for 721 * example, and possibly RDATEs in VTIMEZONEs without an RRULE; these cases will be handled 722 * at a later time. 723 */ 724 private class BlockHash { 725 String name; 726 HashMap<String, String> hash = new HashMap<String, String>(); 727 ArrayList<BlockHash> blocks = new ArrayList<BlockHash>(); 728 729 BlockHash (String _name, BufferedReader reader) throws IOException { 730 name = _name; 731 String lastField = null; 732 String lastValue = null; 733 while (true) { 734 // Get a line; we're done if it's null 735 String line = reader.readLine(); 736 if (line == null) { 737 throw new UnterminatedBlockException(name); 738 } 739 int length = line.length(); 740 if (length == 0) { 741 // We shouldn't ever see an empty line 742 throw new IllegalArgumentException(); 743 } 744 // A line starting with tab is a continuation 745 if (line.charAt(0) == '\t') { 746 // Remember the line and length 747 lastValue = line.substring(1); 748 // Save the concatenation of old and new values 749 hash.put(lastField, hash.get(lastField) + lastValue); 750 continue; 751 } 752 // Find the field delimiter 753 int pos = line.indexOf(':'); 754 // If not found, or at EOL, this is a bad ics 755 if (pos < 0 || pos >= length) { 756 throw new IllegalArgumentException(); 757 } 758 // Remember the field, value, and length 759 lastField = line.substring(0, pos); 760 lastValue = line.substring(pos + 1); 761 if (lastField.equals("BEGIN")) { 762 blocks.add(new BlockHash(lastValue, reader)); 763 continue; 764 } else if (lastField.equals("END")) { 765 if (!lastValue.equals(name)) { 766 throw new UnterminatedBlockException(name); 767 } 768 break; 769 } 770 771 // Save it away and continue 772 hash.put(lastField, lastValue); 773 } 774 } 775 776 String get(String field) { 777 return hash.get(field); 778 } 779 } 780 781 private BlockHash parseIcsContent(byte[] bytes) throws IOException { 782 BufferedReader reader = new BufferedReader(new StringReader(Utility.fromUtf8(bytes))); 783 String line = reader.readLine(); 784 if (!line.equals("BEGIN:VCALENDAR")) { 785 throw new IllegalArgumentException(); 786 } 787 return new BlockHash("VCALENDAR", reader); 788 } 789 790 public void testBuildMessageTextFromEntityValues() { 791 // Set up a test event 792 String title = "Event Title"; 793 Entity entity = setupTestEventEntity(ORGANIZER, ATTENDEE, title); 794 ContentValues entityValues = entity.getEntityValues(); 795 796 // Save this away; we'll use it a few times below 797 Resources resources = mContext.getResources(); 798 Date date = new Date(entityValues.getAsLong(Events.DTSTART)); 799 String dateTimeString = DateFormat.getDateTimeInstance().format(date); 800 801 // Get the text for this message 802 StringBuilder sb = new StringBuilder(); 803 CalendarUtilities.buildMessageTextFromEntityValues(mContext, entityValues, sb); 804 String text = sb.toString(); 805 // We'll just check the when and where 806 assertTrue(text.contains(resources.getString(R.string.meeting_when, dateTimeString))); 807 String location = entityValues.getAsString(Events.EVENT_LOCATION); 808 assertTrue(text.contains(resources.getString(R.string.meeting_where, location))); 809 810 // Make this event recurring 811 entity.getEntityValues().put(Events.RRULE, "FREQ=WEEKLY;BYDAY=MO"); 812 sb = new StringBuilder(); 813 CalendarUtilities.buildMessageTextFromEntityValues(mContext, entityValues, sb); 814 text = sb.toString(); 815 assertTrue(text.contains(resources.getString(R.string.meeting_recurring, dateTimeString))); 816 } 817 818 /** 819 * Sanity test for time zone generation. Most important, make sure that we can run through 820 * all of the time zones without generating an exception. Second, make sure that we're finding 821 * rules for at least 90% of time zones that use daylight time (empirically, it's more like 822 * 95%). Log those without rules. 823 * @throws IOException 824 */ 825 public void testTimeZoneToVTimezone() throws IOException { 826 SimpleIcsWriter writer = new SimpleIcsWriter(); 827 int rule = 0; 828 int nodst = 0; 829 int norule = 0; 830 ArrayList<String> norulelist = new ArrayList<String>(); 831 for (String tzs: TimeZone.getAvailableIDs()) { 832 TimeZone tz = TimeZone.getTimeZone(tzs); 833 writer = new SimpleIcsWriter(); 834 CalendarUtilities.timeZoneToVTimezone(tz, writer); 835 String vc = writer.toString(); 836 boolean hasRule = vc.indexOf("RRULE") > 0; 837 if (hasRule) { 838 rule++; 839 } else if (tz.useDaylightTime()) { 840 norule++; 841 norulelist.add(tz.getID()); 842 } else { 843 nodst++; 844 } 845 } 846 LogUtils.d("TimeZoneGeneration", 847 "Rule: " + rule + ", No DST: " + nodst + ", No rule: " + norule); 848 for (String nr: norulelist) { 849 LogUtils.d("TimeZoneGeneration", "No rule: " + nr); 850 } 851 // This is an empirical sanity test; we shouldn't have too many time zones with DST and 852 // without a rule. 853 assertTrue(norule < rule/8); 854 } 855 856 public void testGetUidFromGlobalObjId() { 857 // This is a "foreign" uid (from some vCalendar client) 858 String globalObjId = "BAAAAIIA4AB0xbcQGoLgCAAAAAAAAAAAAAAAAAAAAAAAAAAAMQAAA" + 859 "HZDYWwtVWlkAQAAADI3NjU1NmRkLTg1MzAtNGZiZS1iMzE0LThiM2JlYTYwMjE0OQA="; 860 String uid = CalendarUtilities.getUidFromGlobalObjId(globalObjId); 861 assertEquals(uid, "276556dd-8530-4fbe-b314-8b3bea602149"); 862 // This is a native EAS uid 863 globalObjId = 864 "BAAAAIIA4AB0xbcQGoLgCAAAAADACTu7KbPKAQAAAAAAAAAAEAAAAObgsG6HVt1Fmy+7GlLbGhY="; 865 uid = CalendarUtilities.getUidFromGlobalObjId(globalObjId); 866 assertEquals(uid, "040000008200E00074C5B7101A82E00800000000C0093BBB29B3CA" + 867 "01000000000000000010000000E6E0B06E8756DD459B2FBB1A52DB1A16"); 868 } 869 870 public void testSelfAttendeeStatusFromBusyStatus() { 871 assertEquals(Attendees.ATTENDEE_STATUS_ACCEPTED, 872 CalendarUtilities.attendeeStatusFromBusyStatus( 873 CalendarUtilities.BUSY_STATUS_BUSY)); 874 assertEquals(Attendees.ATTENDEE_STATUS_TENTATIVE, 875 CalendarUtilities.attendeeStatusFromBusyStatus( 876 CalendarUtilities.BUSY_STATUS_TENTATIVE)); 877 assertEquals(Attendees.ATTENDEE_STATUS_NONE, 878 CalendarUtilities.attendeeStatusFromBusyStatus( 879 CalendarUtilities.BUSY_STATUS_FREE)); 880 assertEquals(Attendees.ATTENDEE_STATUS_NONE, 881 CalendarUtilities.attendeeStatusFromBusyStatus( 882 CalendarUtilities.BUSY_STATUS_OUT_OF_OFFICE)); 883 } 884 885 public void brokentestBusyStatusFromSelfStatus() { 886 assertEquals(CalendarUtilities.BUSY_STATUS_FREE, 887 CalendarUtilities.busyStatusFromAttendeeStatus( 888 Attendees.ATTENDEE_STATUS_DECLINED)); 889 assertEquals(CalendarUtilities.BUSY_STATUS_FREE, 890 CalendarUtilities.busyStatusFromAttendeeStatus( 891 Attendees.ATTENDEE_STATUS_NONE)); 892 assertEquals(CalendarUtilities.BUSY_STATUS_FREE, 893 CalendarUtilities.busyStatusFromAttendeeStatus( 894 Attendees.ATTENDEE_STATUS_INVITED)); 895 assertEquals(CalendarUtilities.BUSY_STATUS_TENTATIVE, 896 CalendarUtilities.busyStatusFromAttendeeStatus( 897 Attendees.ATTENDEE_STATUS_TENTATIVE)); 898 assertEquals(CalendarUtilities.BUSY_STATUS_BUSY, 899 CalendarUtilities.busyStatusFromAttendeeStatus( 900 Attendees.ATTENDEE_STATUS_ACCEPTED)); 901 } 902 903 public void testGetUtcAllDayCalendarTime() { 904 GregorianCalendar correctUtc = new GregorianCalendar(TimeZone.getTimeZone("UTC")); 905 correctUtc.set(2011, 2, 10, 0, 0, 0); 906 long correctUtcTime = correctUtc.getTimeInMillis(); 907 908 TimeZone localTimeZone = TimeZone.getTimeZone("GMT-0700"); 909 GregorianCalendar localCalendar = new GregorianCalendar(localTimeZone); 910 localCalendar.set(2011, 2, 10, 12, 23, 34); 911 long localTimeMillis = localCalendar.getTimeInMillis(); 912 long convertedUtcTime = 913 CalendarUtilities.getUtcAllDayCalendarTime(localTimeMillis, localTimeZone); 914 // Milliseconds aren't zeroed out and may not be the same 915 assertEquals(convertedUtcTime/1000, correctUtcTime/1000); 916 917 localTimeZone = TimeZone.getTimeZone("GMT+0700"); 918 localCalendar = new GregorianCalendar(localTimeZone); 919 localCalendar.set(2011, 2, 10, 12, 23, 34); 920 localTimeMillis = localCalendar.getTimeInMillis(); 921 convertedUtcTime = 922 CalendarUtilities.getUtcAllDayCalendarTime(localTimeMillis, localTimeZone); 923 assertEquals(convertedUtcTime/1000, correctUtcTime/1000); 924 } 925 926 public void testGetLocalAllDayCalendarTime() { 927 TimeZone utcTimeZone = TimeZone.getTimeZone("UTC"); 928 TimeZone localTimeZone = TimeZone.getTimeZone("GMT-0700"); 929 GregorianCalendar correctLocal = new GregorianCalendar(localTimeZone); 930 correctLocal.set(2011, 2, 10, 0, 0, 0); 931 long correctLocalTime = correctLocal.getTimeInMillis(); 932 933 GregorianCalendar utcCalendar = new GregorianCalendar(utcTimeZone); 934 utcCalendar.set(2011, 2, 10, 12, 23, 34); 935 long utcTimeMillis = utcCalendar.getTimeInMillis(); 936 long convertedLocalTime = 937 CalendarUtilities.getLocalAllDayCalendarTime(utcTimeMillis, localTimeZone); 938 // Milliseconds aren't zeroed out and may not be the same 939 assertEquals(convertedLocalTime/1000, correctLocalTime/1000); 940 941 localTimeZone = TimeZone.getTimeZone("GMT+0700"); 942 correctLocal = new GregorianCalendar(localTimeZone); 943 correctLocal.set(2011, 2, 10, 0, 0, 0); 944 correctLocalTime = correctLocal.getTimeInMillis(); 945 946 utcCalendar = new GregorianCalendar(utcTimeZone); 947 utcCalendar.set(2011, 2, 10, 12, 23, 34); 948 utcTimeMillis = utcCalendar.getTimeInMillis(); 949 convertedLocalTime = 950 CalendarUtilities.getLocalAllDayCalendarTime(utcTimeMillis, localTimeZone); 951 // Milliseconds aren't zeroed out and may not be the same 952 assertEquals(convertedLocalTime/1000, correctLocalTime/1000); 953 } 954 955 public void testGetIntegerValueAsBoolean() { 956 ContentValues cv = new ContentValues(); 957 cv.put("A", 1); 958 cv.put("B", 69); 959 cv.put("C", 0); 960 assertTrue(CalendarUtilities.getIntegerValueAsBoolean(cv, "A")); 961 assertTrue(CalendarUtilities.getIntegerValueAsBoolean(cv, "B")); 962 assertFalse(CalendarUtilities.getIntegerValueAsBoolean(cv, "C")); 963 assertFalse(CalendarUtilities.getIntegerValueAsBoolean(cv, "D")); 964 } 965 } 966 967 // TODO Planned unit tests 968 // findNextTransition 969 // recurrenceFromRrule 970 // timeZoneToTziStringImpl 971 // getDSTCalendars 972 // millisToVCalendarTime 973 // millisToEasDateTime 974 // getTrueTransitionMinute 975 // getTrueTransitionHour 976 977