Working with the RecurrencePattern structure in [MS-OXOCAL]
Summary: This article describes how to calculate the FirstDateTime field of the RecurrencePattern structure of the Appointment and Meeting Object Protocol, as defined in [MS-OXOCAL]: Appointment and Meeting Object Protocol Specification. This information will help you learn how to work with a recurring series.
Applies to: Exchange Server
Published: September 2011
Provided by: Robert Knight, Microsoft
Recurrence patterns for calendar items can be complicated. In a recurring series, the calendar part of the series (which does not include the attendees and associated information, such as location) has date and time information for multiple instances of an event that occur regularly according to a pattern. In addition, they can have exceptions and deletions. It is a challenge to store all the information that will describe a set of events in a way that is as small as possible going over the wire, makes sense to both a client and a server, and takes all the required variables into account.
In this article, I will explain how to calculate the FirstDateTime field in the RecurrencePattern structure, as defined in [MS-OXOCAL]: Appointment and Meeting Object Protocol Specification section 2.2.1.44.1. Calculating the FirstDateTime field enables you to derive a daily, weekly, or monthly recurrence pattern based on a starting point of a few fields. This will give you better insight into how recurrence patterns are used and transmitted, and may give you an idea or two for coding new functionality for Microsoft Exchange Server. I also include some code examples that will help you understand the RecurrencePattern structure itself.
The RecurrencePattern structure consists of eighteen fields, including the StartDate field, the Period field, and the FirstDateTime field. I will use the examples in this article to explain how to derive the FirstDateTime field.
Using the FirstDateTime field
You might be asking yourself why you have to know how to calculate the FirstDateTime field. Calculating the FirstDateTime field will help you understand how recurrence patterns work. Maybe you want to write a program that will compare all the holidays in a year with the recurrences of a weekly appointment. Or maybe you want to programmatically match birthdays with team meetings so that you know what day to bring in cake. Whatever the reason, the FirstDateTime field will help you understand how to derive a valid recurrence date based on a date that falls close to a recurrence in your pattern — an input date, or what is known as a "start clip date" — and hopefully give you a better idea of how to work with recurrence patterns.
Now I will show you the values that you need, an algorithm, and some code that you can use to find a valid recurrence in a daily recurrence pattern.
Note
You can download the complete code sample from the MSDN Sample Gallery.
Daily recurrences
First you need a start date for your recurring series (in this example, we’ll use today’s date, as supplied by the DateTime.Today method of the Microsoft .NET Framework), a period, stored as minutes-in-a-day (we’ll use every three days, or 4320 minutes), and a frequency, which is, of course, daily.
DateTime myStartDate;
int myPeriod;
…
case RECUR_FREQUENCY.DAYS_IN_MINUTES:
myPeriod = 4320; // Every three days.
myStartDate = DateTime.Today; // Use today's date.
break;
Dates and times in the .NET Framework are stored in DateTime structures; however, in these calculations I'm using dates that are stored as a total number of minutes – typically from a minimum date (for example, January 1st, 1601 for Gregorian calendars). One advantage to this approach is that when we use minutes for our calculations, we don't have to worry about leap years, days in a month, and other characteristics of calendars that can make the calculations more difficult.
double minutesBetweenMinDateAndStartDate = myRecurrenceValues.startDate.Subtract(minDateGregorian).TotalMinutes;
Now we’re ready to calculate the value of the FirstDateTime field. This value is important because if we subtract the value of the FirstDateTime field from a particular input date, do a modulus of the period, and examine the result (which we’ll call the date offset in minutes), we can determine whether the input date is a valid date in our recurrence. A result of zero indicates that the input date is a valid recurrence date. Otherwise, all we have to do is subtract the date offset from our input date to return a valid recurrence, as shown in the following example.
// Calculate FirstDateTime by taking the start date (as expressed in minutes between minimum date and start date) and modulo the period in minutes.
double FirstDateTime = minutesBetweenMinDateAndStartDate % myRecurrenceValues.Period;
// Find a valid recurrence date given a clip start date.
Random myRnd = new Random();
DateTime inputDate = myRecurrenceValues.startDate.AddDays(myRnd.Next(1, 28));
double clipStartDateInMinutes = inputDate.Subtract(minDateGregorian).TotalMinutes;
double dateOffsetInMinutes = (clipStartDateInMinutes - FirstDateTime) % myRecurrenceValues.Period;
if (dateOffsetInMinutes == 0)
{
myRecurrence = inputDate;
}
else
{
myRecurrence = inputDate.AddMinutes(-dateOffsetInMinutes);
}
In this example, the following is the result:
Start Date: 9/9/2011
Recurrence Frequency: DAYS_IN_MINUTES
Period: Every 4320 (DAYS_IN_MINUTES)
9/21/2011 is a valid recurrence date in the series.
Weekly recurrences
For weekly recurrences, the period is stored in weeks. The following example shows how to calculate a FirstDateTime value for a weekly recurrence.
case RECUR_FREQUENCY.WEEKS:
myPeriod = 2; // Every two weeks.
myStartDate = DateTime.Today.AddYears(-1); // Use a year ago.
break;
…
// Find the date of the first day of the week prior to the start date.
DateTime modifiedStartDate = myRecurrenceValues.startDate.AddDays(-(int)myRecurrenceValues.startDate.DayOfWeek);
// Calculate the number of minutes between midnight that day and midnight January 1, 1601.
double modifiedStartDateInMinutes = modifiedStartDate.Subtract(minDateGregorian).TotalMinutes;
// Take that value modulo the value of the Period field x 10080 (number of minutes in a week) to get the value of the FirstDateTime field.
double FirstDateTime = modifiedStartDateInMinutes % (myRecurrenceValues.Period * 10080);
// Get an input date that is close to the start date. (Add three to seven weeks in days to the start date.)
Random myRnd = new Random();
DateTime inputDate = myRecurrenceValues.startDate.AddDays(myRnd.Next(21, 49));
// Adjust to the start of the week to identify a valid week.
inputDate = inputDate.AddDays(-(int)inputDate.DayOfWeek);
// Get the input date in minutes.
double inputDateInMinutes = inputDate.Subtract(minDateGregorian).TotalMinutes;
DateTime myRecurrence;
// If the date offset is zero, the week is valid. Otherwise, subtract the offset from the input date to find a valid week.
double dateOffsetInMinutes = (inputDateInMinutes - FirstDateTime) % (myRecurrenceValues.Period * 10080);
if (dateOffsetInMinutes == 0)
{
// If the result is zero, the week is valid.
myRecurrence = inputDate;
}
else
{
// If the result is nonzero, this is not a valid week. This value must be subtracted
// from the input date to return a valid week.
myRecurrence = inputDate.AddMinutes(-dateOffsetInMinutes);
}
The following is the result:
Start Date: 9/12/2010
Recurrence Frequency: WEEKS
Period: Every 2 (WEEKS)
10/24/2010 is a valid recurrence date in the series.
Note that we’re looking for a valid week in our pattern. The daily part of a recurrence pattern (for example, in a weekly pattern of every two weeks on Monday, Wednesday, and Friday) is stored in the PatternTypeSpecific field of the recurrence pattern structure.
Monthly/yearly recurrences
Finally, let’s look at calculating the FirstDateTime field for a monthly/yearly recurrence pattern. A yearly pattern is the same as a monthly pattern that has a period that is equal to 12 (months).
For our monthly recurrence, we'll calculate the FirstDateTime value for a date in the Hebrew calendar. When you use a non-Gregorian calendar, some additional considerations apply. In this example, we'll just calculate the FirstDateTime value and stop there.
// Find the first day of the month of the start date.
DateTime modifiedStartDate = myRecurrenceValues.startDate.AddDays(-myHebrewCal.GetDayOfMonth(myRecurrenceValues.startDate) + 1);
// Find the number of months between 9/27/1601 and our start date.
int monthsBetweenTwoDates = CalculateNumberOfMonthsBetweenTwoDates(minDateHebrew, modifiedStartDate);
// Take that number of months and modulo the period to get an offset to add to the minimum Hebrew Calendar date (9/27/1601).
int offsetInMonths = monthsBetweenTwoDates % (myRecurrenceValues.Period);
// Add that number of Hebrew lunar months to 9/27/1601.
DateTime recurrenceOffsetDate = new DateTime();
recurrenceOffsetDate = myHebrewCal.AddMonths(minDateHebrew, offsetInMonths);
// Calculate the number of minutes between midnight that day and midnight, January 1, 1601. This is our recurrence offset (FirstDateTime), in minutes.
double FirstDateTime = new TimeSpan(recurrenceOffsetDate.Ticks - minDateGregorian.Ticks).TotalMinutes;
Conclusion
The cool thing about the FirstDateTime field is that if you have a start date, an end date, a period, and a recurrence frequency, you can reconstruct the whole recurrence pattern (excluding exceptions and deletions). Not too bad a trick using only five numbers. Hopefully calculating FirstDateTime and deriving the recurrence pattern with a start clip date helps you understand recurrence patterns and how they're used. It might even give you an idea or two about some interesting things that you can do with them.
See Also
Other Resources
[MS-OXOCAL]: Appointment and Meeting Object Protocol Specification