Best Practices for Using Exchange Web Services for Calendaring Tasks
This content is no longer actively maintained. It is provided as is, for anyone who may still be using these technologies, with no warranties or claims of accuracy with regard to the most recent product version or service release.
Topic Last Modified: 2007-09-04
Exchange Web Services in Microsoft® Exchange Server 2007 exposes a rich set of Web methods and types that you can use to create, update, and generate calendar workflow between Exchange 2007 mailboxes. These methods, while powerful, can also cause a lot of confusion and unforeseen consequences when they are used incorrectly.
It is tempting for developers to create a few examples of basic calendar appointments and assume that if the start and end times look correct, everything is okay. However, applications and databases that do not adequately account for the complexities of time zones can encounter many problems.
In this article, I provide the information that you need to ensure that the calendar data that you create is complete, and adequately protected against time zone–related changes. I also provide sample code that you can use in your own application for several key calendaring tasks. To view the sample code, download the TimeChangeType Extension Sample from the Microsoft Download Center.
Calendaring Terms and Concepts
Before we delve into the calendaring best practices, it is important that you become familiar with some terms and concepts. In this section, I will describe the terms and concepts that you will need to understand before you use Exchange Web Services for calendaring tasks.
Serialization
Serialization is the process by which an object type and its associated property values are converted into a string. That string can then be used to transfer the object and its associated values between two parties. Exchange Web Services uses XML serialization.
Deserialization
Deserialization is the process by which a string representation of an object type and its property values is converted back into a usable type instance. In other words, deserialization is the conversion of a serialized object back into its preserialized form.
XML Schema Part 2
The XML Schema Part 2 is a standard that was proposed by the World Wide Web Consortium (W3C) that describes string formats for the transfer of data and the serialization/deserialization of data into supported types. The following are some of the XML Schema types that are used in this article:
- xs:time
- xs:date
- xs:datetime
- xs:duration
For information about the specification for XML Schema Part 2, see XML Schema Part 2: Datatypes Second Edition.
Note
The third-party Web site information in this article is provided to help you find the technical information that you need. The URLs are subject to change without notice.
UTC-Offset String
A UTC-offset string is a date/time string that complies with the XML Schema Part 2 specification that includes a time zone offset with either the ‘Z’ flag or as ([-]d{2}:d{2}).
The following are examples of UTC-offset strings:
- 02-14-2006T08:30:00Z
- 04-01-1996T07:45:00-08:00
- 05-16-1978Z
- 12-10-2032T13:45:14+05:30
Non-UTC-Offset String
A non-UTC-offset string is a string that does not have a time zone offset. This is also known as a Local time string. The following are examples of non-UTC-offset strings:
- 11-15-2007T04:15:00
- 07-02-2015
Note
In this article, I will not use the term Local in the context of date/time strings. Because this term has different meanings in different contexts, I will use the term Local only in the context of the Microsoft .NET System.DateTime structure.
DateTimeKind Enumeration
DateTimeKind is a .NET enumeration that specifies time zone information on System.DateTime instances. This enumeration can be read via the .Kind property. The DateTimeKind enumeration is used during serialization of a DateTime structure to determine whether the resulting string should be a UTC-offset string or a non-UTC-offset string.
The following table lists the possible values for the DateTimeKind enumeration.
DateTimeKind enumeration values
DateTimeKind | Serialized xs:datetime result | Example |
---|---|---|
Local |
UTC-offest |
04-04-2002T14:31:54+06:00 |
UTC |
UTC-offset |
01-15-2008T16:01:01Z |
Unspecified |
Non-UTC-offset |
11-24-1904T04:15:26 |
When deserialization of an xs:datetime string occurs, the .Kind property will always be Local regardless of the state of the source string with regard to UTC offset.
Time Zone Registry Keys
Time zone registry keys are found in the following path of the Microsoft Windows® registry: HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Time Zones\. Windows stores all supported time zone definitions in this location, each under a separate key name. The time zone definition in the registry contains, but is not limited to, the following information:
- English display name
- Daylight and Standard names
- Offsets into resource DLLs to find localized display names
- A Time Zone Info (TZI) byte array that encodes the following:
- Base offset
- Standard offset and time change information
- Daylight offset and time change information
For more information about the time zone registry definition, including information about how to parse the TZI byte array, see TIME_ZONE_INFORMATION.
xs:duration Strings
Exchange Web Services requires that BaseOffsets and Offsets be specified by using xs:duration strings. (For a definition of xs:duration, see XML Schema Part 2: Datatypes Second Edition). The value of the string represents the Bias for the time zone. A Bias for a time zone is valid if it satisfies the following equation:
UTC = Time + Bias
In this equation, Time is a non-UTC-offset string.
The following table provides some examples of BaseOffsets that are specified as xs:duration.
Examples of BaseOffsets
Time Zone Key Name | English Display Name | Base Offset in Minutes | xs:duration Equivalent |
---|---|---|---|
Pacific Standard Time |
(GMT-08:00) Pacific Time (US & Canada) |
480 |
PT8H or PT480M |
AUS Eastern Standard Time |
(GMT+10:00) Canberra, Melbourne, Sydney |
-600 |
-PT10H |
Nepal Standard Time |
(GMT+05:45) Kathmandu |
-345 |
PT5H45M |
GMT Standard Time |
(GMT) Greenwich Mean Time : Dublin, Edinburgh, Lisbon, London |
0 |
PT0H |
MeetingTimeZone
The MeetingTimeZone property defines the time zone for a CalendarItem. A MeetingTimeZone property is populated by using a TimeZoneType instance.
The TimeZoneType is an Exchange Web Services type that describes a fully qualified static time zone definition. (In the initial release version of Exchange 2007, Exchange Web Services does not support Dynamic Time Zones.) A TimeZoneType structure contains an optional TimeZoneName element, a required BaseOffset element, and two optional structures that define Standard and Daylight transition times if the time zone in question supports it. To review the TimeZoneType structure for the initial release of Exchange 2007, see the types.xsd schema file.
In Exchange Server 2007 Service Pack 1 (SP1), BaseOffset is an optional element, and supports MeetingTimeZones that specify the TimeZoneName only.
The Standard and Daylight elements of a TimeZoneType are defined as TimeChangeType instances. The TimeChangeType includes the following:
- An offset.
- A specified Time property.
- One of two possible TimeChangePatternTypes, which define the date of the time change. This date is represented as either an absolute date, or a relative yearly change pattern.
To review the TimeChangePatternType structure for the initial release of Exchange 2007, see the types.xsd schema file.
The following table lists and describes the elements of a fully qualified MeetingTimeZone property.
Fully qualified MeetingTimeZone property
Element | Definition | Example |
---|---|---|
Time zone key name |
The key name of the time zone that this structure represents. |
<MeetingTimeZone TimeZoneName=”(GMT -08:00) Pacific Time (US & Canada)”> |
Base offset |
The default offset for a time zone, which is defined as an xs:duration string. |
<BaseOffset>PT8H</BaseOffset> |
Additional offsets |
Additional offsets to apply to the base offset during standard and daylight saving times. (Standard offsets are always zero.) |
<Offset>-PT1H</Offset> |
Time change date |
A structure (either an absolute date, or a relative yearly change pattern) that represents the date when a time zone should change from standard to daylight saving time, and vice versa. |
<RelativeYearlyRecurrence> <DaysOfWeek>Sunday</> <DayOfWeekIndex>First</> <Month>November</> </RelativeYearlyRecurrence> |
Time change time |
An xs:time string that represents the time at which the additional offset should be applied. |
|
Note
In the initial release version of Exchange 2007, the time zone key name field is read-only, and only applies to the fetching of appointments that are created by using protocols other than Exchange Web Services.
The following are some examples of MeetingTimeZone properties.
Key name: US Mountain Standard Time
English Display Name: (GMT-07:00) Arizona
Daylight Time: No
<MeetingTimeZone TimeZoneName="(GMT-07:00) Arizona">
<BaseOffset>P0DT7H0M0.0S</BaseOffset>
</MeetingTimeZone>
Key name: Pacific Standard Time
English Display Name: (GMT-08:00) Pacific Time (US & Canada)
Daylight Time: Yes
<MeetingTimeZone TimeZoneName="(GMT-08:00) Pacific Time (US & Canada)">
<BaseOffset>P0DT8H0M0.0S</BaseOffset>
<Standard>
<Offset>P0DT0H0M0.0S</Offset>
<RelativeYearlyRecurrence>
<DaysOfWeek>Sunday</DaysOfWeek>
<DayOfWeekIndex>First</DayOfWeekIndex>
<Month>November</Month>
</RelativeYearlyRecurrence>
<Time>02:00:00.0000000</Time>
</Standard>
<Daylight>
<Offset>-P0DT1H0M0.0S</Offset>
<RelativeYearlyRecurrence>
<DaysOfWeek>Sunday</DaysOfWeek>
<DayOfWeekIndex>Second</DayOfWeekIndex>
<Month>March</Month>
</RelativeYearlyRecurrence>
<Time>02:00:00.0000000</Time>
</Daylight>
</MeetingTimeZone>
Key name: Cen. Australia Standard Time
English Display Name: (GMT+09:30) Adelaide
Daylight Time: Yes
<MeetingTimeZone TimeZoneName="(GMT+09:30) Adelaide">
<BaseOffset>-P0DT9H30M0.0S</BaseOffset>
<Standard>
<Offset>P0DT0H0M0.0S</Offset>
<RelativeYearlyRecurrence>
<DaysOfWeek>Sunday</DaysOfWeek>
<DayOfWeekIndex>Last</DayOfWeekIndex>
<Month>March</Month>
</RelativeYearlyRecurrence>
<Time>03:00:00.0000000</Time>
</Standard>
<Daylight>
<Offset>-P0DT1H0M0.0S</Offset>
<RelativeYearlyRecurrence>
<DaysOfWeek>Sunday</DaysOfWeek>
<DayOfWeekIndex>Last</DayOfWeekIndex>
<Month>October</Month>
</RelativeYearlyRecurrence>
<Time>02:00:00.0000000</Time>
</Daylight>
</MeetingTimeZone>
UTC Conversion
When Exchange Web Services receives a request to create a new CalendarItem for which the Start and End properties are identified by non-UTC-offset strings, the server must convert the Start and End properties to Coordinated Universal Time (UTC) before the CalendarItem can be stored. The following are the rules for the conversion to UTC:
If the request contains an explicit time zone definition via a MeetingTimeZone property, the server will apply the correct offset with regard to Standard and Daylight rules as defined by the time zone.
If no explicit time zone is defined, the current time zone of the computer that is running Exchange 2007 (specifically, the Client Access server that is processing the request) will be used.
Note
In Exchange 2007 SP1, all unspecified time zones are set to UTC instead of the time zone of the Client Access server.
Best Practices for Creating Calendar Items
In this section I will introduce some best practices that will help you create calendar items by using Exchange Web Services.
Include a MeetingTimeZone Element
To make sure that the appointments that you create will handle future changes to time zone rules, include a fully qualified time zone definition for every calendar item by using a MeetingTimeZone element.
The following example shows you the XML that is used to create a new single CalendarItem that includes a time zone definition.
<soap:Envelope
xmlns:soap="https://schemas.xmlsoap.org/soap/envelope/" \
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<m:CreateItem SendMeetingInvitations="SendToNone"
xmlns:m="https://schemas.microsoft.com/exchange/services/2006/messages"
xmlns:t="https://schemas.microsoft.com/exchange/services/2006/types">
<m:Items>
<t:CalendarItem>
<t:Subject>Single Meeting with TZ Info</t:Subject>
<t:Start>2007-01-16T19:00:00</t:Start>
<t:End>2007-01-16T23:00:00</t:End>
<t:MeetingTimeZone>
<t:BaseOffset>PT7H</t:BaseOffset>
</t:MeetingTimeZone>
</t:CalendarItem>
</m:Items>
</m:CreateItem>
</soap:Body>
</soap:Envelope>
Only having an offset, or only storing times in UTC, is insufficient to identify which appointments must be adjusted when time zone rules change. By including a MeetingTimeZone element, you make sure that any calendar items that you create by using Exchange Web Services can be updated successfully if time zone rules change in the future.
We also recommend that you use non-UTC-offset strings to express all Start and End times. This makes the handling of date/time strings consistent across your application.
Do not supply the optional TimeZoneName element when you create calendar items. In the initial release version of Exchange 2007, the TimeZoneName element is ignored on incoming requests.
Note
Because the TimeZoneName element is ignored in incoming requests, all CalendarItems that you create by using Exchange Web Services will be stamped with an Unknown Timezone entry in the Exchange store.
The following example shows you the equivalent C# code for creating a CalendarItem that includes a time zone definition.
/// <param name="binding">Fully initialized Exchange Service Binding</param>
public static void Fig2_CreateSingleMeetingWithTZInfo(ExchangeServiceBinding binding)
{
CalendarItemType newCalendarItem = new CalendarItemType();
newCalendarItem.Subject = "Single Meeting with TZ Info";
// As a best practice, all DateTime structures should be initialized
// with DateTimeKind.Unspecified. As a result,
// the corresponding date/time strings are non-UTC-offset strings. Because we are also
// going to supply a TimeZoneType with our CalendarItem, this is acceptable.
DateTime startTime = new DateTime(
2007, 01, 16, 19, 00, 00, DateTimeKind.Unspecified);
newCalendarItem.Start = startTime;
newCalendarItem.End = startTime.AddHours(4);
newCalendarItem.StartSpecified = newCalendarItem.EndSpecified = true;
// Construct a TimeZoneType that will be used to set the MeetingTimeZone
// of our new CalendarItem.
TimeZoneType tzUSMST = new TimeZoneType();
tzUSMST.BaseOffset = "PT7H";
// Note that we are setting the MeetingTimeZone property and not the TimeZone
// property. The TimeZone property is a read-only property that the server populates
// when doing a get.
newCalendarItem.MeetingTimeZone = tzUSMST;
// Construct the CreateItem request.
CreateItemType createItemRequest = new CreateItemType();
createItemRequest.SendMeetingInvitations =
CalendarItemCreateOrDeleteOperationType.SendToNone;
createItemRequest.SendMeetingInvitationsSpecified = true;
createItemRequest.Items = new NonEmptyArrayOfAllItemsType();
createItemRequest.Items.Items = new CalendarItemType[] { newCalendarItem };
// Invoke the CreateItem Web Service method (TODO: Check for errors in the response).
CreateItemResponseType createItemResponse =
binding.CreateItem(createItemRequest);
}
Use Non-UTC-Offset Strings for Date/Time Strings in Recurrence Patterns
When you use non-UTC-offset strings for the date/time strings in recurrence patterns, you make sure that the occurrences are always calculated from a correctly adjusted start and end time.
Note
End times are optional based on the recurrence pattern that you select.
The following code example shows you the SOAP/XML that is used to create a new recurring CalendarItem that includes time zone information.
<soap:Envelope
xmlns:soap="https://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<m:CreateItem SendMeetingInvitations="SendToNone"
xmlns:m="https://schemas.microsoft.com/exchange/services/2006/messages"
xmlns:t="https://schemas.microsoft.com/exchange/services/2006/types">
<m:Items>
<t:CalendarItem>
<t:Subject>Four Day Conference in Phoenix</t:Subject>
<t:Start>2007-11-02T08:00:00</t:Start>
<t:End>2007-11-02T09:00:00</t:End>
<t:Recurrence>
<t:DailyRecurrence>
<t:Interval>1</t:Interval>
</t:DailyRecurrence>
<t:NumberedRecurrence>
<t:StartDate>2007-11-02</t:StartDate>
<t:NumberOfOccurrences>4</t:NumberOfOccurrences>
</t:NumberedRecurrence>
</t:Recurrence>
<t:MeetingTimeZone>
<t:BaseOffset>PT7H</t:BaseOffset>
</t:MeetingTimeZone>
</t:CalendarItem>
</m:Items>
</m:CreateItem>
</soap:Body>
</soap:Envelope>
The following code example shows you the equivalent C# proxy code that is used to create a new recurring CalendarItem.
/// <param name="binding">Fully initialized Exchange Service Binding</param>
public static void Fig4_CreateRecurringMeetingWithTZInfo(ExchangeServiceBinding binding)
{
CalendarItemType newCalendarItem = new CalendarItemType();
newCalendarItem.Subject = "Four Day Conference in Phoenix";
// As a best practice, initialize all DateTime structures with
// DateTimeKind.Unspecified. This includes those that you will use in your
// recurrence pattern definition.
DateTime appointmentStartTime = new DateTime(
2007, 11, 02, 08, 00, 00, DateTimeKind.Unspecified);
DateTime recurrenceStartTime = new DateTime(
2007, 11, 02, 00, 00, 00, DateTimeKind.Unspecified);
newCalendarItem.Start = appointmentStartTime;
newCalendarItem.End = appointmentStartTime.AddHours(1);
newCalendarItem.StartSpecified = newCalendarItem.EndSpecified = true;
// Construct the recurrence pattern definition.
DailyRecurrencePatternType pattern = new DailyRecurrencePatternType();
pattern.Interval = 1;
NumberedRecurrenceRangeType range = new NumberedRecurrenceRangeType();
range.NumberOfOccurrences = 4;
range.StartDate = recurrenceStartTime;
newCalendarItem.Recurrence = new RecurrenceType();
newCalendarItem.Recurrence.Item = pattern;
newCalendarItem.Recurrence.Item1 = range;
// Construct a TimeZoneType that will be used to set the MeetingTimeZone
// of the new recurring CalendarItem.
TimeZoneType tzUSMST = new TimeZoneType();
tzUSMST.BaseOffset = "PT7H";
newCalendarItem.MeetingTimeZone = tzUSMST;
// Construct the CreateItem request.
CreateItemType createItemRequest = new CreateItemType();
createItemRequest.SendMeetingInvitations =
CalendarItemCreateOrDeleteOperationType.SendToNone;
createItemRequest.SendMeetingInvitationsSpecified = true;
createItemRequest.Items = new NonEmptyArrayOfAllItemsType();
createItemRequest.Items.Items = new CalendarItemType[] { newCalendarItem };
// Invoke the CreateItem Web Service method (TODO: Check for errors in the response).
CreateItemResponseType createItemResponse =
binding.CreateItem(createItemRequest);
}
Fully Qualify MeetingTimeZone Elements
If a time zone definition includes standard and daylight saving time change rules, make sure that you always fully supply those rules.
The following code example shows you the SOAP/XML that is used to create a new recurring CalendarItem that includes time zone changes.
<soap:Envelope
xmlns:soap="https://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<m:CreateItem SendMeetingInvitations="SendToNone"
xmlns:m="https://schemas.microsoft.com/exchange/services/2006/messages"
xmlns:t="https://schemas.microsoft.com/exchange/services/2006/types">
<m:Items>
<t:CalendarItem>
<t:Subject>Four Day Conference in Billings</t:Subject>
<t:Start>2007-11-02T08:00:00</t:Start>
<t:End>2007-11-02T09:00:00</t:End>
<t:Recurrence>
<t:DailyRecurrence>
<t:Interval>1</t:Interval>
</t:DailyRecurrence>
<t:NumberedRecurrence>
<t:StartDate>2007-11-02</t:StartDate>
<t:NumberOfOccurrences>4</t:NumberOfOccurrences>
</t:NumberedRecurrence>
</t:Recurrence>
<t:MeetingTimeZone>
<t:BaseOffset>PT7H</t:BaseOffset>
<t:Standard>
<t:Offset>PT0M</t:Offset>
<t:RelativeYearlyRecurrence>
<t:DaysOfWeek>Sunday</t:DaysOfWeek>
<t:DayOfWeekIndex>First</t:DayOfWeekIndex>
<t:Month>November</t:Month>
</t:RelativeYearlyRecurrence>
<t:Time>02:00:00</t:Time>
</t:Standard>
<t:Daylight>
<t:Offset>-PT60M</t:Offset>
<t:RelativeYearlyRecurrence>
<t:DaysOfWeek>Sunday</t:DaysOfWeek>
<t:DayOfWeekIndex>Second</t:DayOfWeekIndex>
<t:Month>March</t:Month>
</t:RelativeYearlyRecurrence>
<t:Time>02:00:00</t:Time>
</t:Daylight>
</t:MeetingTimeZone>
</t:CalendarItem>
</m:Items>
</m:CreateItem>
</soap:Body>
</soap:Envelope>
It is important to include fully qualified time change rules for single appointments as well as for recurring appointments. Single instanced appointments can be converted to recurring appointments by adding a recurrence pattern after the appointment is created. When you make sure that the time zone is fully qualified when you create a CalendarItem, you can later add or adjust a recurrence pattern without worrying about miscalculations that may occur after a time change rule takes effect.
Note
Exchange Web Services proxy users cannot specify a non-UTC-offset string for the Time part of the TimeChangeType unless they implement the IXmlSerializable workaround. For information about the IXmlSerializable workaround, see "Creating the IXmlSerializable Partial Class" later in this article.
The following code example shows you the C# proxy code that is used to create a new recurring CalendarItem that includes time zone changes.
/// <param name="binding">Fully initialized Exchange Service Binding</param>
public static void Fig6_CreateMeetingWithChangingTZ(ExchangeServiceBinding binding)
{
CalendarItemType newCalendarItem = new CalendarItemType();
newCalendarItem.Subject = "Four Day Conference in Billings";
// As a best practice, initialize all DateTime structures with
// DateTimeKind.Unspecified. This includes those that you will use in your
// recurrence pattern definition.
DateTime appointmentStartTime = new DateTime(
2007, 11, 02, 08, 00, 00, DateTimeKind.Unspecified);
DateTime recurrenceStartTime = new DateTime(
2007, 11, 02, 00, 00, 00, DateTimeKind.Unspecified);
newCalendarItem.Start = appointmentStartTime;
newCalendarItem.End = appointmentStartTime.AddHours(1);
newCalendarItem.StartSpecified = newCalendarItem.EndSpecified = true;
// Construct the recurrence pattern definition.
DailyRecurrencePatternType pattern = new DailyRecurrencePatternType();
pattern.Interval = 1;
NumberedRecurrenceRangeType range = new NumberedRecurrenceRangeType();
range.NumberOfOccurrences = 4;
range.StartDate = recurrenceStartTime;
newCalendarItem.Recurrence = new RecurrenceType();
newCalendarItem.Recurrence.Item = pattern;
newCalendarItem.Recurrence.Item1 = range;
// Construct a TimeZoneType for a time zone that complies with daylight
// saving time. This requires that you fill in the optional Standard
// and Daylight structures in the TimeZoneType.
//
TimeZoneType tzMST = new TimeZoneType();
tzMST.BaseOffset = "PT7H";
tzMST.Standard = new TimeChangeType();
tzMST.Standard.Offset = "PT0H";
// For the Time part of a TimeChangeType, the date is
// unimportant. It will be omitted during serialization.
//
// There is, however, an issue that will require that you
// implement an IXmlSerializable interface to control the formatting
// of the DateTime string that you generate here.
tzMST.Standard.Time = new DateTime(
2007, 01, 01, 02, 00, 00, DateTimeKind.Unspecified);
// Define the day of the time change as a RelativeYearlyRecurrencePattern.
RelativeYearlyRecurrencePatternType stdTimeChangeDay =
new RelativeYearlyRecurrencePatternType();
stdTimeChangeDay.DaysOfWeek = DayOfWeekType.Sunday.ToString();
stdTimeChangeDay.DayOfWeekIndex = DayOfWeekIndexType.First;
stdTimeChangeDay.Month = MonthNamesType.November;
// The .Item property on the TimeChangeType is designed to hold either
// a RelativeYearlyRecurrencePattern or
// an AbsoluteDateType. Most time zones require a RelativeYearlyRecurrencePattern, but some
// will require a RelativeYearlyRecurrencePattern.
tzMST.Standard.Item = stdTimeChangeDay;
// Repeat this process for Daylight change information as well.
tzMST.Daylight = new TimeChangeType();
tzMST.Daylight.Offset = "-PT1H";
tzMST.Daylight.Time = new DateTime(
2007, 01, 01, 02, 00, 00, DateTimeKind.Unspecified);
RelativeYearlyRecurrencePatternType dltTimeChangeDay =
new RelativeYearlyRecurrencePatternType();
dltTimeChangeDay.DaysOfWeek = DayOfWeekType.Sunday.ToString();
dltTimeChangeDay.DayOfWeekIndex = DayOfWeekIndexType.Second;
dltTimeChangeDay.Month = MonthNamesType.March;
tzMST.Daylight.Item = dltTimeChangeDay;
newCalendarItem.MeetingTimeZone = tzMST;
// Construct the CreateItem request.
CreateItemType createItemRequest = new CreateItemType();
createItemRequest.SendMeetingInvitations =
CalendarItemCreateOrDeleteOperationType.SendToNone;
createItemRequest.SendMeetingInvitationsSpecified = true;
createItemRequest.Items = new NonEmptyArrayOfAllItemsType();
createItemRequest.Items.Items = new CalendarItemType[] { newCalendarItem };
// Invoke the CreateItem Web Service method (TODO: Check for errors in the response).
CreateItemResponseType createItemResponse =
binding.CreateItem(createItemRequest);
}
Use Registry Key Names for TimeZoneNames
In Exchange 2007 SP1, you can specify a fully qualified time zone by its registry key name via the TimeZoneName attribute on the MeetingTimeZone element. For example:
<t:MeetingTimeZone TimeZoneName="US Mountain Standard Time">
When you use a key name to identify a time zone, omit the BaseOffset, Standard, and Daylight elements from the MeetingTimeZone element. The Exchange server will then obtain and use the most up-to-date version of the time zone definition from its own registry.
Note
During time zone rule updates, the client and the server may be out of sync. If this occurs, the client should use the values from the server and adjust appointments accordingly.
Working Around the Serialization Problem with the Exchange Web Services C# Proxy
Ordinarily, a System.DateTime structure represents an xs:time field in the C# proxy classes. During the serialization/deserialization process, the date information that is contained within the System.DateTime structure is ignored and only the time part of the structure persists. However, when you are using the Microsoft .NET Framework serializer, the serialization process from System.DateTime to xs:time includes the time zone offset of the time zone of the application that is invoking the serializer.
The following code example illustrates this serialization problem.
public static void Fig7_TZSerializationProblem()
{
RelativeYearlyRecurrencePatternType rptAUSDaylight = new RelativeYearlyRecurrencePatternType();
rptAUSDaylight.DaysOfWeek = DayOfWeekType.Sunday.ToString();
rptAUSDaylight.DayOfWeekIndex = DayOfWeekIndexType.Last;
rptAUSDaylight.Month = MonthNamesType.October;
TimeChangeType daylightChangeForAUS = new TimeChangeType();
daylightChangeForAUS.TimeZoneName = "AUS Eastern Standard Time";
daylightChangeForAUS.Offset = "-PT60M";
daylightChangeForAUS.Item = rptAUSDaylight;
// Define Time Change time as Unspecified so that it serializes
// as a non-UTC-offset string.
// Remember, the .Time field is a DateTime instance, but the
// date portion really doesn't matter, only the time.
daylightChangeForAUS.Time = new DateTime(2007, 11, 28, 03, 00, 00, DateTimeKind.Unspecified);
// Output to the console the results of serializing the TimeChangeType.
System.Xml.Serialization.XmlSerializer mySerializer =
new System.Xml.Serialization.XmlSerializer(typeof(TimeChangeType));
mySerializer.Serialize(System.Console.Out, daylightChangeForAUS);
}
The following example shows you the output of this code. It represents the TimeChangeType that was created in its XML serialized form.
<TimeChangeType TimeZoneName="AUS Eastern Standard Time" xmlns="http://.../types">
<Offset>-PT60M</Offset>
<RelativeYearlyRecurrence>
<DaysOfWeek>Sunday</DaysOfWeek>
<DayOfWeekIndex>Last</DayOfWeekIndex>
<Month>October</Month>
</RelativeYearlyRecurrence>
<Time>03:00:00.0000000-07:00</Time>
</TimeChangeType>
Notice that, although the DateTime structure was created with DateTimeKind.Unspecified, the Time element still contains a time zone offset. This behavior can result in an offset of an hour or more on the calculated recurrences for appointments. To correct this behavior, you must create a partial class for the TimeChangeType that implements IXmlSerializable. This intercepts the XML reading and writing process during serialization and prevents the offset flag from being written.
Creating an IXmlSerializable Partial Class
Before an Exchange Web Services proxy application can implement an IXmlSerializable interface for TimeChangeType, it must first make sure that the autogenerated class does not compile with the System.XmlSerialzation.XmlTypeAttribute.
The following code example shows you the commented out XmlTypeAttribute for the TimeChangeType.
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.42")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
//[System.Xml.Serialization.XmlTypeAttribute(Namespace="https://schemas.microsoft.com/exchange/services/2006/types")]
public partial class TimeChangeType { ... }
When the XmlTypeAttribute is commented out, the .NET Framework will call any of the IXmlSerializable methods directly. The IXmlSerializable interface includes the following three methods:
- GetSchema
- ReadXml
- WriteXml
For information about the IXmlSerializable interface, see IXmlSerializable Interface.
Let’s look now at some key features of our new partial class for the TimeChangeType.
To download the code sample, see TimeChangeType Extension Sample. Note that to use the code sample, you must do the following:
- Modify the namespace to match that of the TimeChangeType in your own autogenerated proxy library or code file.
- Compile the code directly with your autogenerated proxy code.
The first thing to do in your partial class is to indicate that the class implements IXmlSerializable. You must also create an empty constructor to prevent runtime issues during the serialization/deserialization process. You can see this in the ProxyObjects namespace and the TimeChangeType class declaration in the code file.
Next, implement the GetSchema method, which must return a System.Xml.Schema.XmlSchema object. This XmlSchema object returns the information about the TimeChangeType as if it came directly from the types.xsd file that was used to create the autogenerated proxy. You can see this by viewing the GetSchema method of the TimeChangeType partial class in the code file.
The ReadXml method must handle the reading of all the serialized XML for a TimeChangeType, including any nested types. Also, because this method will be receiving an XmlReader that is pointing to one node of XML in a potentially must larger XML document, you must make sure that you consume the whole TimeChangeType node. You do this by grabbing the XmlReader.LocalName immediately in the method, and then as you iterate through the XML, find the corresponding ending XML tag that matches this value to indicate that you are finished. You can see this in the ReadXml method of the TimeChangeType partial class in the code file.
Finally, the WriteXml method receives an XmlWriter object. Put the serialized XML for the TimeChangeType into the XmlWriter object. The XmlWriter instance that you get points to a particular location within a larger XML document. Therefore, you must make sure that you are writing all the XML for this TimeChangeType object.
You finally get an opportunity to correct the problem with the Time element output itself in the WriteXml method. The solution is to honor the DateTimeKind property that exists on the Time variable, and output a correctly formatted string accordingly.
You are also using an XmlSerializer for any RelativeYearlyRecurrencePatternType that might be set on your TimeChangeType object. This way, you don't have to worry about incorrectly writing XML for this nested type. Instead, you ask the .NET Framework to call the IXmlSerializable interface on that type, and hand back the XML that you can then put in its correct location. To see this, review the WriteXml method for the TimeChangeType partial class in the code file.
The following code example shows you the new output when you include the IXmlSerializable partial class in your executable.
<TimeChangeType xmlns="https://schemas.microsoft.com/exchange/services/2006/types" TimeZoneName="AUS Eastern Standard Time">
<Offset>-PT60M</Offset>
<RelativeYearlyRecurrence>
<DaysOfWeek>Sunday</DaysOfWeek>
<DayOfWeekIndex>Last</DayOfWeekIndex>
<Month>October</Month>
</RelativeYearlyRecurrence>
<Time>03:00:00</Time>
</TimeChangeType>
You can see that the time zone offset is no longer included in the Time element. This ensures that when you create TimeChangeType objects for use in your code, you get the correct deserialized version of the data, and your appointments are created correctly.