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