Manage Appointments: Create & Manipulate, Convert ICS to MSG

This article covers standards-based iCalendar (ICS) appointments handled through the Appointment class. To work with Outlook MapiCalendar items instead, see Managing Outlook Calendar Items; to store and read calendar items inside a PST, see Managing Calendar Items in PST Files.

Create an Appointment and Save to Disk in MSG or ICS Format

The Appointment class in Aspose.Email for .NET can be used to create a new appointment. In this article, we first create an appointment and save it to a disk in ICS format. The following steps are required to create an appointment and save it to a disk.

  1. Create an instance of the Appointment class and initialize it with this constructor.
  2. Pass the following arguments in the above constructor
    1. Location
    2. Summary
    3. Description
    4. Start Date
    5. End Date
    6. Organizer
    7. Attendees
  3. Call the Save() method and specify the file name and format in the arguments.

The appointment can be opened in Microsoft Outlook or any program that can load an ICS file. If the file is opened in Microsoft Outlook it automatically adds the appointment in the Outlook calendar.

The following code snippet shows you how to create and save an appointment to a disk in ICS or MSG format.


// Create and initialize an instance of the Appointment class
Appointment appointment = new Appointment(
    "Meeting Room 3 at Office Headquarters",// Location
    "Monthly Meeting",                      // Summary
    "Please confirm your availability.",    // Description
    new DateTime(2015, 2, 8, 13, 0, 0),     // Start date
    new DateTime(2015, 2, 8, 14, 0, 0),     // End date
    "from@domain.com",                      // Organizer
    "attendees@domain.com");                // Attendees

// Save the appointment to disk in ICS format            
appointment.Save(fileName + ".ics", new AppointmentIcsSaveOptions());
Console.WriteLine("Appointment created and saved to disk successfully.");

// Save the appointment to disk in MSG format
appointment.Save(fileName + ".msg", new AppointmentMsgSaveOptions());
Console.WriteLine("Appointment created and saved to disk successfully.");

Create an Appointment with HTML Content

You can specify alternative representations of the event’s description in different content types using the X-ALT-DESC header. It allows recipients of the iCalendar file to choose the representation that best suits their needs. For example, you may include a plain text description using the “text/plain” content type and an HTML description using the “text/html” content type. The X-ALT-DESC header is added for each alternative representation. To create an appointment with HTML content, set the HtmlDescription property.

Try the following code sample to create an appointment with alternative HTML description:

  1. Create a new instance of the Appointment class.
  2. Provide the necessary parameters to the Appointment constructor:
    • Specify the location of the appointment.
    • Set the start date and time.
    • Set the end date and time.
    • Specify the organizer.
    • Specify the attendee.
  3. Set the HtmlDescription property of the appointment object, indicating that the description is in HTML format.
  4. Set the Description property of the appointment object to an HTML-formatted string, enclosed within a multiline string:
    • The HTML markup includes a <style> block defining a CSS class named “text” with font styles.
    • The HTML body contains a paragraph tag <p> with the CSS class “text”, and the actual invitation message.
  5. The appointment object is now ready, and you can perform further operations or save it as an iCalendar file.
var appointment = new Appointment("Bygget 83",
    DateTime.UtcNow, // start date
    DateTime.UtcNow.AddHours(1), // end date
    new MailAddress("TintinStrom@from.com", "Tintin Strom"), // organizer
    new MailAddress("AinaMartensson@to.com", "Aina Martensson")) // attendee
{
    HtmlDescription = @"
    <html>
     <style type=""text/css"">
      .text {
             font-family:'Comic Sans MS';
             font-size:16px;
            }
     </style>
    <body>
     <p class=""text"">Hi, I'm happy to invite you to our party.</p>
    </body>
    </html>"
};

Create a Draft Appointment Request

It was shown in our earlier articles how to create and save an appointment in ICS format. It is often required to create an Appointment request in a Draft mode, so as the basic information is added and then the same draft Appointment be forwarded to other users for necessary changes according to individual uses. In order to save an Appointment in a Draft mode, the MethodType property of Appointment class should be set to AppointmentMethodType.Publish. The following code snippet shows you how to create a draft appointment request.

string sender = "test@gmail.com";
string recipient = "test@email.com";

MailMessage message = new MailMessage(sender, recipient, string.Empty, string.Empty);

Appointment app = new Appointment(string.Empty, DateTime.Now, DateTime.Now, sender, recipient)
{
    MethodType = AppointmentMethodType.Publish
};

message.AddAlternateView(app.RequestApointment());

MapiMessage msg = MapiMessage.FromMailMessage(message);

// Save the appointment as draft.
msg.Save(dstDraft);

Console.WriteLine(Environment.NewLine + "Draft saved at " + dstDraft);

Draft Appointment Creation from Text

The following code snippet shows you how to create a draft appointment from Text. 

string ical = @"BEGIN:VCALENDAR
METHOD:PUBLISH
PRODID:-//Aspose Ltd//iCalender Builder (v3.0)//EN
VERSION:2.0
BEGIN:VEVENT
ATTENDEE;CN=test@gmail.com:mailto:test@gmail.com
DTSTART:20130220T171439
DTEND:20130220T174439
DTSTAMP:20130220T161439Z
END:VEVENT
END:VCALENDAR";

string sender = "test@gmail.com";
string recipient = "test@email.com";
MailMessage message = new MailMessage(sender, recipient, string.Empty, string.Empty);
AlternateView av = AlternateView.CreateAlternateViewFromString(ical, new ContentType("text/calendar"));
message.AlternateViews.Add(av);
MapiMessage msg = MapiMessage.FromMailMessage(message);
msg.Save(dataDir + "draft_out.msg");

Customize Appointments

Set Participants Status of Appointment Attendees

Aspose.Email for .NET API lets you set the status of appointment attendees while formulating a reply message. This adds the PARTSTAT property to the ICS file.

DateTime startDate = new DateTime(2011, 12, 10, 10, 12, 11),
         endDate = new DateTime(2012, 11, 13, 13, 11, 12);
MailAddress organizer = new MailAddress("aaa@amail.com", "Organizer");
MailAddressCollection attendees = new MailAddressCollection();
MailAddress attendee1 = new MailAddress("bbb@bmail.com", "First attendee");
MailAddress attendee2 = new MailAddress("ccc@cmail.com", "Second attendee");

attendee1.ParticipationStatus = ParticipationStatus.Accepted;
attendee2.ParticipationStatus = ParticipationStatus.Declined;
attendees.Add(attendee1);
attendees.Add(attendee2);

Appointment target = new Appointment(location, startDate, endDate, organizer, attendees);

Customize Product Identifier for ICalendar

Aspose.Email for .NET API allows to get or set the product identifier that created iCalendar object.

string description = "Test Description";
Appointment app = new Appointment("location", "test appointment", description, DateTime.Today,
DateTime.Today.AddDays(1), "first@test.com", "second@test.com");

AppointmentIcsSaveOptions saveOptions = AppointmentIcsSaveOptions.Default;
saveOptions.ProductId = "Test Corporation";
app.Save(dataDir + "ChangeProdIdOfICS.ics", saveOptions);

Loading Appointments

Also, the Appointment class can be used to load an appointment from ICS file.

Load an Appointment in ICS Format

To load an appointment in ICS format, the following steps are required:

  1. Create an instance of the Appointment class.
  2. Call the Load() method by providing the path of the ICS file.
  3. Read any property to get any information from the appointment (ICS file).

The following code snippet shows you how to load an appointment in ICS format.

// Load an Appointment just created and saved to disk and display its details.
Appointment loadedAppointment = Appointment.Load(dstEmail);
Console.WriteLine(Environment.NewLine + "Loaded Appointment details are as follows:");
// Display the appointment information on screen
Console.WriteLine("Summary: " + loadedAppointment.Summary);
Console.WriteLine("Location: " + loadedAppointment.Location);
Console.WriteLine("Description: " + loadedAppointment.Description);
Console.WriteLine("Start date: " + loadedAppointment.StartDate);
Console.WriteLine("End date: " + loadedAppointment.EndDate);
Console.WriteLine("Organizer: " + loadedAppointment.Organizer);
Console.WriteLine("Attendees: " + loadedAppointment.Attendees);
Console.WriteLine(Environment.NewLine + "Appointment loaded successfully from " + dstEmail);

Convert ICS to MSG

The API allows you easily convert an Appointment to a message object. The following code example shows how to convert an appointment request into a MailMessage or MapiMessage:

var appointment = Appointment.Load("appRequest.ics");

var eml = appointment.ToMailMessage();
var msg = appointment.ToMapiMessage();

Read Multiple Events from ICS File

List<Appointment> appointments = new List<Appointment>();
CalendarReader reader = new CalendarReader(dataDir + "US-Holidays.ics");

while (reader.NextEvent())
{
    appointments.Add(reader.Current);
}
//working with appointments...

Write Multiple Events to ICS File

AppointmentIcsSaveOptions saveOptions = new AppointmentIcsSaveOptions();
saveOptions.Action = AppointmentAction.Create;
using (CalendarWriter writer = new CalendarWriter(dataDir + "WriteMultipleEventsToICS_out.ics", saveOptions))
{
    for (int i = 0; i < 10; i++)
    {
        Appointment app = new Appointment(string.Empty, DateTime.Now, DateTime.Now, "sender@domain.com", "receiver@domain.com");
        app.Description = "Test body " + i;
        app.Summary = "Test summary:" + i;
        writer.Write(app);
    }
}

Determine the Appointment Version

To determine the version of an appointment, you can use the Appointment.Version property of the Appointment class. This property assists to determine which version their files are based on, ensuring integration with other systems and apps.

The following code sample shows how to implement this property in your project:

var app = Appointment.Load("meeting.ics");

// Version is a string that holds the ICS/VCS version, e.g. "2.0" for iCalendar
if (app.Version == "2.0")
{
    // do something
}

Sending and Cancelling Meeting Requests

The Appointment class, together with the SmtpClient, can be used to send meeting requests, recurring meeting requests, updates, and cancellations by email. The appointment is added to a MailMessage as an alternate view.

Send a Meeting Request

To send a meeting request, create an Appointment, add it to a message using the RequestApointment method, and send the message. Save the appointment’s UniqueId value so you can reference the same appointment later when sending an update or a cancellation.

// Create an instance of SmtpClient
SmtpClient client = new SmtpClient("smtp.gmail.com", 587, "user@gmail.com", "password");
client.SecurityOptions = SecurityOptions.Auto;

// Gather the attendees
MailAddressCollection attendees = new MailAddressCollection();
attendees.Add(new MailAddress("first.attendee@domain.com", "First Attendee"));
attendees.Add(new MailAddress("second.attendee@domain.com", "Second Attendee"));

// Create the message and the appointment
MailMessage msg = new MailMessage();
msg.From = "organizer@domain.com";
msg.To = attendees;

Appointment app = new Appointment("Meeting Room 1", DateTime.Now, DateTime.Now.AddHours(1), msg.From, attendees);
app.Summary = "Monthly Meeting";
app.Description = "Please confirm your availability.";

// Add the appointment to the message and send it
msg.AddAlternateView(app.RequestApointment());
client.Send(msg);

Send a Recurring Meeting Request

To create a meeting request with recurrence, assign a recurrence pattern (for example, a WeeklyRecurrencePattern) to the Appointment.Recurrence property. Saving the appointment’s unique ID makes it possible to send updates to the appointment later.

// Create a mail message
MailMessage msg1 = new MailMessage();
msg1.To.Add("to@domain.com");
msg1.From = new MailAddress("from@gmail.com");

// Fill the appointment object
DateTime startDate = new DateTime(2013, 12, 1, 17, 0, 0);
DateTime endDate = new DateTime(2013, 12, 31, 17, 30, 0);
Appointment agendaAppointment = new Appointment("same place", startDate, endDate, msg1.From, msg1.To);

// Create a unique id so the appointment can be accessed later
string szUniqueId = Guid.NewGuid().ToString();
agendaAppointment.UniqueId = szUniqueId;
agendaAppointment.Description = "----------------";

// Create a weekly recurrence pattern: Mon, Tue and Thu
WeeklyRecurrencePattern pattern1 = new WeeklyRecurrencePattern(14);
pattern1.StartDays = new CalendarDay[3];
pattern1.StartDays[0] = CalendarDay.Monday;
pattern1.StartDays[1] = CalendarDay.Tuesday;
pattern1.StartDays[2] = CalendarDay.Thursday;
pattern1.Interval = 1;

// Set the recurrence pattern for the appointment
agendaAppointment.Recurrence = pattern1;

// Attach the appointment to the mail
msg1.AlternateViews.Add(agendaAppointment.RequestApointment());

// Send the mail with the appointment request
SmtpClient client = new SmtpClient("smtp.gmail.com", 587, "your.email@gmail.com", "your.password");
client.SecurityOptions = SecurityOptions.Auto;
client.Send(msg1);

Send an Appointment Update Request

To send an update for a previously sent appointment, the appointment’s unique ID is required. Use the UpdateAppointment method to build the update alternate view.

static public void SendUpdate(string szUniqueId)
{
    DateTime startDate = new DateTime(2013, 12, 12, 17, 0, 0);
    DateTime endDate = new DateTime(2013, 12, 12, 17, 30, 0);
    Appointment appUpdate = new Appointment("Different Place", startDate, endDate,
        "organizer@gmail.com", "attendee@domain.com");
    appUpdate.UniqueId = szUniqueId;
    appUpdate.Summary = "update meeting request summary";
    appUpdate.Description = "update";

    MailMessage msgUpdate = new MailMessage("organizer@gmail.com", "attendee@domain.com",
        "test email - update meeting request", "test email");
    msgUpdate.AddAlternateView(appUpdate.UpdateAppointment());

    SmtpClient smtp = new SmtpClient("server.domain.com", 587, "username", "password");
    smtp.Send(msgUpdate);
}

Cancel a Meeting Request

To cancel a meeting, build the same Appointment (using the information you stored when the request was sent), add it to a message with the CancelAppointment method, and send the message to the attendees.

// Re-create the attendee collection and the appointment from your stored data
MailAddressCollection attendees = new MailAddressCollection();
attendees.Add(new MailAddress("first.attendee@domain.com", "First Attendee"));
attendees.Add(new MailAddress("second.attendee@domain.com", "Second Attendee"));

Appointment app = new Appointment("Meeting Room 1", "Monthly Meeting", "Please confirm your availability.",
    DateTime.Now, DateTime.Now.AddHours(1),
    new MailAddress("organizer@domain.com", "Organizer"), attendees);

// Create the cancellation message
MailMessage msg = new MailMessage();
msg.From = new MailAddress("organizer@domain.com", "");
msg.To = attendees;
msg.Subject = "Cancel meeting";
msg.AddAlternateView(app.CancelAppointment());

SmtpClient smtp = new SmtpClient("smtp.gmail.com", 587, "user@gmail.com", "password");
smtp.Send(msg);

Working with iCalendar Recurrence Patterns

A recurrence pattern is a way to describe a particular schedule. It contains just enough information to build a list of occurrences (dates and times) according to a given schedule. A recurrence pattern may contain recurrence rules that describe cycles that combine to form the overall pattern. In general, the more complex a recurrence pattern is, the more recurrence rules it will contain.

Recurrence patterns can include exceptions (not to be confused with exceptions that represent errors during application execution). Exceptions add or remove occurrence dates relative to the original pattern. They can be specified as explicit occurrences or as a pattern themselves. Examples of recurrence patterns with exceptions:

  • Every 2nd Friday, except from June through August.
  • The 1st of every month, except for January, when it should be on the 2nd.

The iCalendar RFC defines components, such as VEVENT or VTODO, that represent events or tasks. The components can have properties such as start date/time, description, location, attendees, and recurrence. A recurrence pattern normally exists as a property of a recurring task or event. The recurrence pattern properties defined by iCalendar are:

  • DTSTART – the start date and time of the pattern (also represents the first occurrence if not excluded explicitly).
  • RRULE – specifies a repeating rule for a recurrence set.
  • RDATE – defines a list of dates and times to include in a recurrence set.
  • EXRULE – specifies a repeating rule for exceptions from a recurrence set.
  • EXDATE – defines a list of date and time exceptions from a recurrence set.

Only DTSTART is required, and there must be only one DTSTART. All other properties are optional and can be specified more than once.

The Aspose.Email Recurrence Object Model

The Aspose.Email.Calendar.Recurrences namespace contains the classes used to work with iCalendar recurrences. CalendarRecurrence and RecurrenceRule are the central classes, and they provide concrete implementations of the corresponding RFC 2445 elements.

  • The CalendarRecurrence class represents the whole recurrence pattern. You can create a new recurrence pattern from scratch using the default constructor, or load an existing pattern in iCalendar format using the static FromiCalendar method.
  • The RecurrenceRule class represents the RRULE or EXRULE part of a recurrence pattern. RecurrenceRule exposes a number of properties that directly map to their counterparts in the iCalendar standard. For example, ByMonth maps to BYMONTH in iCalendar, and so on. By examining or setting values of the RecurrenceRule properties, you can analyze or modify a recurrence rule.

The following code snippet loads a recurrence pattern (in which the RRULE part contains the recurrence rule) and generates the occurrences:

// Ten team meetings, every Monday at 10am.
CalendarRecurrence pattern = new CalendarRecurrence("DTSTART:20040301T100000\n" + "RRULE:FREQ=WEEKLY;COUNT=10;BYDAY=MO");
DateCollection dates = pattern.GenerateOccurrences();

Generate Occurrences from a Recurrence Pattern

With Aspose.Email it is possible to generate occurrences from a recurrence pattern. To get the “next” occurrence, use the GenerateOccurrences method with the parameter nNextOccurrences = 1. The following code snippet generates 20 occurrences by using GenerateOccurrences(20).

CalendarRecurrence recurrencePattern = new CalendarRecurrence();
recurrencePattern.StartDate = new DateTime(1997, 9, 10, 9, 0, 0);
RecurrenceRule rule = recurrencePattern.RRules.Add();
rule.Frequency = Frequency.Monthly;
rule.Count = 20;
rule.Interval = 18;
rule.ByMonthDay.Add(new int[] { 10, 11, 12, 13, 14, 15 });
DateCollection expectedDates = recurrencePattern.GenerateOccurrences(20);
Console.WriteLine("expectedDates.Count = " + expectedDates.Count);
foreach (DateTime date in expectedDates)
{
    Console.WriteLine("DateTime = " + date);
}

Get User-Friendly Text for a Recurrence

User-friendly text for a rule can be obtained using the FriendlyText property. The output of the following code is: “Recur every month on the 1st and 1st from end day(s) of the month for a maximum of 2 occurrences.”

RecurrenceRule rule = new RecurrenceRule();
rule.Frequency = Frequency.Monthly;
rule.Count = 2;
rule.ByMonthDay.Add(1);
rule.ByMonthDay.Add(-1);
Console.WriteLine(rule.FriendlyText);

Sample Recurrence Patterns

The following sample RRULE strings illustrate how to express common schedules.

  • The last day of the month, every month. If you want an occurrence on the day before the last day of the month, use BYMONTHDAY=-2. If you specify BYMONTHDAY=31, then according to the iCalendar standard, no occurrence is generated in months that have fewer than 31 days.

    RRULE:FREQ=MONTHLY;BYMONTHDAY=-1
    
  • The last workday of every month. This rule specifies all workdays of a month and selects the last one. The net result is the last workday in a month.

    RRULE:FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1
    
  • The last Monday of the year.

    RRULE:FREQ=YEARLY;BYDAY=-1MO
    
  • Friday of the first ISO 8601 week of the year. In the ISO 8601 specification, the first week of the year is the first one with at least four days.

    FREQ=YEARLY;BYWEEKNO=1;BYDAY=FR
    
  • First Friday of the year. In 1999, for example, the 1st Friday of the year is 1999/01/01, while the Friday of the 1st ISO 8601 week is 1999/01/08.

    FREQ=YEARLY;BYDAY=1FR
    

Important iCalendar (RFC 2445) Details

Dates, or dates with associated times, can be used in the DTSTART, UNTIL, EXDATE, and RDATE elements when specifying a recurrence pattern. iCalendar defines the DATE value type to identify values that contain a calendar date, and the DATE-TIME type to identify values that specify a precise calendar date and time of day. DATE-TIME values can be specified in three forms: local time, UTC time, and local time with a time zone.

  • DATE. According to the iCalendar standard, DATE values must follow the yyyyMMdd format. For example, 19970714 represents July 14, 1997.
  • DATE-TIME with local time. The date with local time form is simply a date-time value that does not contain the UTC designator and does not reference a time zone. For example, DTSTART:19980118T230000 represents January 18, 1998, at 11 PM. Date-time values of this type are said to be “floating” and are not bound to any particular time zone. They represent the same hour, minute, and second value regardless of the time zone currently being observed.
  • DATE-TIME with UTC time. The date with UTC time (absolute time) is identified by a capital letter Z suffix appended to the time value. For example, DTSTART:19980119T070000Z represents January 19, 1998, at 0700 UTC. Note that Aspose.Email ignores the UTC Z suffix and treats the time as local time. The RFC 2445 standard states that a time portion specified in the UNTIL rule of a recurrence pattern must be in UTC format; Aspose.Email accepts time in any format in the UNTIL rule.
  • DATE-TIME with local time and time zone. To reference a time zone, DATE-TIME is modified with the TZID property. For example, DTSTART;TZID=US-Eastern:19980119T020000 represents 2 AM in New York on January 19, 1998. Note that Aspose.Email currently ignores the TZID parameter and treats the time as local time.
  • BYWEEKNO and ISO 8601 compliance. Use BYWEEKNO only when conformance with ISO 8601 is required. Week numbers as defined by ISO 8601 differ from week numbers in the normal sense: week number one of the calendar year is the first week that contains at least four days. The BYWEEKNO rule specifies a comma-delimited list of numbers identifying weeks of the year (valid values are 1 to 53 and -1 to -53), and it is only valid for YEARLY rules.

See Also