Fix the VCALENDAR we send with all day events

* We need to send date only (without time) in the VCALENDAR file for
  all-day events
* Add unit test for this case

Bug: 2561789
Change-Id: I33a43c7a248059c97482ca147a23af083744118a
This commit is contained in:
Marc Blank 2010-03-31 19:24:18 -07:00
parent 1b3166e84a
commit 1c48450c02
2 changed files with 114 additions and 24 deletions

View File

@ -795,28 +795,32 @@ public class CalendarUtilities {
* Generate an EAS formatted date/time string based on GMT. See below for details.
*/
static public String millisToEasDateTime(long millis) {
return millisToEasDateTime(millis, sGmtTimeZone);
return millisToEasDateTime(millis, sGmtTimeZone, true);
}
/**
* Generate an EAS formatted local date/time string from a time and a time zone
* Generate an EAS formatted local date/time string from a time and a time zone. If the final
* argument is false, only a date will be returned (e.g. 20100331)
* @param millis a time in milliseconds
* @param tz a time zone
* @return an EAS formatted string indicating the date/time in the given time zone
* @param withTime if the time is to be included in the string
* @return an EAS formatted string indicating the date (and time) in the given time zone
*/
static public String millisToEasDateTime(long millis, TimeZone tz) {
static public String millisToEasDateTime(long millis, TimeZone tz, boolean withTime) {
StringBuilder sb = new StringBuilder();
GregorianCalendar cal = new GregorianCalendar(tz);
cal.setTimeInMillis(millis);
sb.append(cal.get(Calendar.YEAR));
sb.append(formatTwo(cal.get(Calendar.MONTH) + 1));
sb.append(formatTwo(cal.get(Calendar.DAY_OF_MONTH)));
sb.append('T');
sb.append(formatTwo(cal.get(Calendar.HOUR_OF_DAY)));
sb.append(formatTwo(cal.get(Calendar.MINUTE)));
sb.append(formatTwo(cal.get(Calendar.SECOND)));
if (tz == sGmtTimeZone) {
sb.append('Z');
if (withTime) {
sb.append('T');
sb.append(formatTwo(cal.get(Calendar.HOUR_OF_DAY)));
sb.append(formatTwo(cal.get(Calendar.MINUTE)));
sb.append(formatTwo(cal.get(Calendar.SECOND)));
if (tz == sGmtTimeZone) {
sb.append('Z');
}
}
return sb.toString();
}
@ -1245,15 +1249,28 @@ public class CalendarUtilities {
// Our default vcalendar time zone is UTC, but this will change (below) if we're
// sending a recurring event, in which case we use local time
TimeZone vCalendarTimeZone = sGmtTimeZone;
String vCalendarTimeZoneSuffix = "";
String vCalendarDateSuffix = "";
// Check for all day event
boolean allDayEvent = false;
if (entityValues.containsKey(Events.ALL_DAY)) {
Integer ade = entityValues.getAsInteger(Events.ALL_DAY);
allDayEvent = (ade != null) && (ade == 1);
if (allDayEvent) {
// Example: DTSTART;VALUE=DATE:20100331 (all day event)
vCalendarDateSuffix = ";VALUE=DATE";
}
}
// If we're inviting people and the meeting is recurring, we need to send our time zone
// information and make sure to send DTSTART/DTEND in local time
if (!isReply && entityValues.containsKey(Events.RRULE)) {
// information and make sure to send DTSTART/DTEND in local time (unless, of course,
// this is an all-day event)
if (!isReply && entityValues.containsKey(Events.RRULE) && !allDayEvent) {
vCalendarTimeZone = TimeZone.getDefault();
// Write the VTIMEZONE block to the writer
timeZoneToVTimezone(vCalendarTimeZone, ics);
vCalendarTimeZoneSuffix = ";TZID=" + vCalendarTimeZone.getID();
// Example: DTSTART;TZID=US/Pacific:20100331T124500
vCalendarDateSuffix = ";TZID=" + vCalendarTimeZone.getID();
}
ics.writeTag("BEGIN", "VEVENT");
@ -1272,23 +1289,24 @@ public class CalendarUtilities {
long startTime = entityValues.getAsLong(Events.DTSTART);
if (startTime != 0) {
ics.writeTag("DTSTART" + vCalendarTimeZoneSuffix,
millisToEasDateTime(startTime, vCalendarTimeZone));
ics.writeTag("DTSTART" + vCalendarDateSuffix,
millisToEasDateTime(startTime, vCalendarTimeZone, !allDayEvent));
}
// If this is an Exception, we send the recurrence-id, which is just the original
// instance time
if (isException) {
long originalTime = entityValues.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
ics.writeTag("RECURRENCE-ID" + vCalendarTimeZoneSuffix,
millisToEasDateTime(originalTime, vCalendarTimeZone));
ics.writeTag("RECURRENCE-ID" + vCalendarDateSuffix,
millisToEasDateTime(originalTime, vCalendarTimeZone, !allDayEvent));
}
if (!entityValues.containsKey(Events.DURATION)) {
if (entityValues.containsKey(Events.DTEND)) {
ics.writeTag("DTEND" + vCalendarTimeZoneSuffix,
ics.writeTag("DTEND" + vCalendarDateSuffix,
millisToEasDateTime(
entityValues.getAsLong(Events.DTEND), vCalendarTimeZone));
entityValues.getAsLong(Events.DTEND), vCalendarTimeZone,
!allDayEvent));
}
} else {
// Convert this into millis and add it to DTSTART for DTEND
@ -1300,9 +1318,9 @@ public class CalendarUtilities {
} catch (ParseException e) {
// We'll use the default in this case
}
ics.writeTag("DTEND" + vCalendarTimeZoneSuffix,
ics.writeTag("DTEND" + vCalendarDateSuffix,
millisToEasDateTime(
startTime + durationMillis, vCalendarTimeZone));
startTime + durationMillis, vCalendarTimeZone, !allDayEvent));
}
String location = null;

View File

@ -265,6 +265,70 @@ public class CalendarUtilitiesTests extends AndroidTestCase {
//TODO Check the contents of the attachment using an iCalendar parser
}
public void testCreateMessageForEntity_Invite_AllDay() throws IOException {
// Set up the "event"
String title = "Discuss Unit Tests";
Entity entity = setupTestEventEntity(ORGANIZER, ATTENDEE, title);
entity.getEntityValues().put(Events.ALL_DAY, 1);
// Create a dummy account for the attendee
Account account = new Account();
account.mEmailAddress = ORGANIZER;
// The uid is required, but can be anything
String uid = "31415926535";
// Create the outgoing message
Message msg = CalendarUtilities.createMessageForEntity(mContext, entity,
Message.FLAG_OUTGOING_MEETING_INVITE, uid, account);
// First, we should have a message
assertNotNull(msg);
// Now check some of the fields of the message
assertEquals(Address.pack(new Address[] {new Address(ATTENDEE)}), msg.mTo);
String accept = getContext().getResources().getString(R.string.meeting_invitation, title);
assertEquals(accept, msg.mSubject);
// And make sure we have an attachment
assertNotNull(msg.mAttachments);
assertEquals(1, msg.mAttachments.size());
Attachment att = msg.mAttachments.get(0);
// And that the attachment has the correct elements
assertEquals("invite.ics", att.mFileName);
assertEquals(Attachment.FLAG_ICS_ALTERNATIVE_PART,
att.mFlags & Attachment.FLAG_ICS_ALTERNATIVE_PART);
assertEquals("text/calendar; method=REQUEST", att.mMimeType);
assertNotNull(att.mContentBytes);
assertEquals(att.mSize, att.mContentBytes.length);
// We'll check the contents of the ics file here
BlockHash vcalendar = parseIcsContent(att.mContentBytes);
assertNotNull(vcalendar);
// We should have a VCALENDAR with a REQUEST method
assertEquals("VCALENDAR", vcalendar.name);
assertEquals("REQUEST", vcalendar.get("METHOD"));
// We should have one block under VCALENDAR
assertEquals(1, vcalendar.blocks.size());
BlockHash vevent = vcalendar.blocks.get(0);
// It's a VEVENT with the following fields
assertEquals("VEVENT", vevent.name);
assertEquals("Meeting Location", vevent.get("LOCATION"));
assertEquals("0", vevent.get("SEQUENCE"));
assertEquals("Discuss Unit Tests", vevent.get("SUMMARY"));
assertEquals(uid, vevent.get("UID"));
assertEquals("MAILTO:" + ATTENDEE,
vevent.get("ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE"));
// These next two fields should have a date only
assertEquals("20100412", vevent.get("DTSTART;VALUE=DATE"));
assertEquals("20100412", vevent.get("DTEND;VALUE=DATE"));
// This should be set to TRUE for all-day events
assertEquals("TRUE", vevent.get("X-MICROSOFT-CDO-ALLDAYEVENT"));
}
public void testCreateMessageForEntity_Invite() throws IOException {
// Set up the "event"
String title = "Discuss Unit Tests";
@ -320,6 +384,14 @@ public class CalendarUtilitiesTests extends AndroidTestCase {
assertEquals(uid, vevent.get("UID"));
assertEquals("MAILTO:" + ATTENDEE,
vevent.get("ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE"));
// These next two fields should exist (without the VALUE=DATE suffix)
assertNotNull(vevent.get("DTSTART"));
assertNotNull(vevent.get("DTEND"));
assertNull(vevent.get("DTSTART;VALUE=DATE"));
assertNull(vevent.get("DTEND;VALUE=DATE"));
// This shouldn't exist for this event
assertNull(vevent.get("X-MICROSOFT-CDO-ALLDAYEVENT"));
}
public void testCreateMessageForEntity_Exception_Cancel() throws IOException {
@ -396,8 +468,8 @@ public class CalendarUtilitiesTests extends AndroidTestCase {
long originalTime = entityValues.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
assertNotSame(0, originalTime);
// For an exception, RECURRENCE-ID is critical
assertEquals(CalendarUtilities.millisToEasDateTime(originalTime, timeZone),
vevent.get("RECURRENCE-ID" + ";TZID=" + timeZone.getID()));
assertEquals(CalendarUtilities.millisToEasDateTime(originalTime, timeZone,
true /*withTime*/), vevent.get("RECURRENCE-ID" + ";TZID=" + timeZone.getID()));
}
public void testUtcOffsetString() {