The recurrence logic for yearly recurrence appears to be incorrect

• Question

• p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 9.0px Verdana}

MS-OXOCAL section 2.2.1.44.1 RecurrencePattern structure, gives the following advice on computing FirstDateTime from StartDate.

1.Find the first day of the month of StartDate.

p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 9.0px Verdana}

2.Determine MinimumDate. For Gregorian calendars, this is midnight, January 1, 1601.For non-Gregorian calendars, this is the first day of the calendar's year that falls in the Gregorian year of 1601. For example, if the CalendarType is CAL_HEBREW, the first day of that calendar's year that falls in the Gregorian year of 1601 is 1/1/5362, which is the Gregorian date of 9/27/1601.

3.Calculate the number of calendar months between midnight of the days calculated in step 1 and step 2.

4.Take that value modulo Period. 5.Add that number of months to the MinimumDate, as determined in

step 2.

6.Calculate the number of minutes between midnight that day and midnight, January 1, 1601.

However, when I try to replicate this logic myself, everything goes wrong.

Sample code in Java (I removed generalisation to all calendar systems, which is handled by our APIs, and hard-coded it to Hebrew): https://gist.github.com/d44052736cee7f741a76

I have checked all the results and they appear to be correct for each step, but the end result is not what is stored in the FirstDateTime value for the calendar entry, which is 727200 (0xB18A0).

Thinking laterally, the instructions say "take that value modulo Period", which to me sounds like it might be making the assumption that there are always 12 months in the year.  This is obviously not the case, so are the steps wrong?

Tuesday, May 31, 2011 1:50 AM

• Hi, here is the correct way to calculate the FirstDateTime value when the calendar type is CAL_HEBREW.

1. Find the first day of the month of StartDate.
1. Original day = 5/22/2011
1. First day of month of StartDate = 5/1/2011

1. Determine MinimumDate.
1. The document provides the answer for this one, its 9/27/1601.

1. Calculate the number of calendar months between midnight of the days calculated in step 1 and step 2.
1. How many years have passed? 2011 - 1601 = 410.
2. Multiplied by 12 months in a year. 410 * 12 = 4920.
1. Plus how many months until the month the appointment occurs in this year which is the (month number - 1). 4920 + (5 - 1) = 4924
1. Subtract how many months passed in 1601 before our start date which is the (month number - 1). 4924 - (9 - 1) = 4916

1. Take that value modulo Period.
1. 4916 MOD 12 = 8

1. Add that number of months to the MinimumDate, as determined in step 2.
1. This is the tricky part. You can't actually just add 8 to the month value, that would give you 5/27/1602. The correct way to do this is to take the value from step 4 and multiply that by the number of days in a lunar month (29.53). Drop the fractional value and you get 236 days. Add 236 days to 9/27/1601 and you get the date 5/21/1602.

1. Calculate the number of minutes between midnight that day and midnight, January 1, 1601.
1. There are 505 days between Jan 1, 1601 and May 21, 1602
1. 505 * 1440 = 727200

Please let me know if you have any questions.

Josh Curry (jcurry) | Escalation Engineer | US-CSS DSC Protocols Team
Friday, July 15, 2011 2:29 PM

All replies

• AlexVes,

Thank you for your question.  A member of the Protocols team will research your issue and respond soon.

Bryan S. Burgin Senior Escalation Engineer Microsoft Protocol Open Specifications Team
Tuesday, May 31, 2011 2:21 AM
• Hi AlexVas0765574, I am the engineer who will be working with you on this issue. I am currently researching the problem and will provide you with an update soon. Thank you for your patience.
Josh Curry (jcurry) | Escalation Engineer | US-CSS DSC Protocols Team
Tuesday, May 31, 2011 2:08 PM
• Hi AlexVas0765574, I was able to follow the process in MS-OXOCAL section 2.2.1.44.1 and get the expected value in FirstDateTime. Here is how I did it…

I created a yearly recurring appointment that starts on June 1, 2011. Using MFCMAPI, I was able to get the values of the PidLidAppointmentRecur structure.

WriterVersion: 0x3004

RecurFrequency: 0x200D = IDC_RCEV_PAT_ORB_YEARLY

PatternType: 0x0002 = rptMonth

CalendarType: 0x0000 = CAL_DEFAULT

FirstDateTime: 0x00035160 = 217440

Period: 0x0000000C = 12

SlidingFlag: 0x00000000

PatternTypeSpecific.MonthRecurrencePattern: 0x00000001 = 1

EndType: 0x00002022 = IDC_RCEV_PAT_ERB_AFTERNOCCUR

OccurrenceCount: 0x0000000A = 10

FirstDOW: 0x00000000 = Sunday

DeletedInstanceCount: 0x00000000 = 0

ModifiedInstanceCount: 0x00000000 = 0

StartDate: 0x0CDDB380 = 215856000 = 12:00:00.000 AM 6/1/2011

EndDate: 0x0D25F280 = 220590720 = 12:00:00.000 AM 6/1/2020

Appointment Recurrence Pattern:

WriterVersion2: 0x00003009

StartTimeOffset: 0x000001E0 = 480 = 08:00:00.000 AM 1/1/1601

EndTimeOffset: 0x000001FE = 510 = 08:30:00.000 AM 1/1/1601

ExceptionCount: 0x0000

ReservedBlock1Size: 0x00000000

ReservedBlock2Size: 0x00000000

Then I followed the process:

1.       Find the first day of the month of StartDate.

a.       No need in my case since it's June 1, 2011.

2.       Determine MinimumDate.

a.       In our case this will be January 1, 1601.

3.       Calculate the number of calendar months between midnight of the days calculated in step 1 and step 2.

a.       How many years have passed? 2011 - 1601 = 410.

b.      Multiplied by 12 months in a year. 410 * 12 = 4,920.

c.       Plus how many months until the month the appointment occurs in this year which is the month number - 1. 4920 + (6 - 1) = 4925.

4.       Take that value modulo Period.

a.       4925 MOD 12 = 5

5.       Add that number of months to the MinimumDate, as determined in step 2.

a.       Jan 1, 1601 + 5 months = June 1, 1601.

1.       Calculate the number of minutes between midnight that day and midnight, January 1, 1601.

a.       There are 151 days between Jan 1 and June 1, 1601.

b.      Multiply that by 1440 minutes in a day and you get 217440, which is the value in FirstDateTime.

Please let me know if this helps.

Josh Curry (jcurry) | Escalation Engineer | US-CSS DSC Protocols Team
Tuesday, May 31, 2011 7:08 PM
• Yes, when the calendar is CAL_GREGORIAN (as is the case for CAL_DEFAULT), the computations work for me too.

Could you try doing the same sequence for CAL_HEBREW_LUNAR?  This is the situation where doing a "mod 12" is suspicious, because there aren't always 12 months in the year.

Wednesday, June 1, 2011 1:25 AM
• e.g. StartDate = 26/5/2011 = 22 Iyar 5571 in the Hebrew Lunar calendar.  This is 22/9/5571 under MS's scheme where the first month of the year is not the first month of the year of the actual Hebrew calendar according to Wikipedia, but since this is MS's docs, I will assume that it is month 9.

1.       Find the first day of the month of StartDate.

a.       Assuming that this means in the local calendar, 1/9/5571

2.       Determine MinimumDate.

a.      Since we're not using the Gregorian calendar this would usually require computation, but the docs helpfully give the answer for the Hebrew calendar, which is 1/1/5362.

3.       Calculate the number of calendar months between midnight of the days calculated in step 1 and step 2.

a.       Because months in the Hebrew calendar are not all 12 months, I can't perform the simple multiplication by 12 trick above, and have to resort to using a real calendar.  Because the number of years is large enough to be impractical, I decided to write code for it which printed out each year for sanity's sake.  The end result: 5066.

4.       Take that value modulo Period.

a.       5066 MOD 12 = 2

5.       Add that number of months to the MinimumDate, as determined in step 2.

a.       1/1/5362 + 2 months = 1/3/5362 (1 Kislev 5362)

1.       Calculate the number of minutes between midnight that day and midnight, January 1, 1601.

a.       There are 28339200000 milliseconds according to the calendar class.

b.      Divide that by (60*1000) and you get 472320.  But the value I see in the actual data is 727200.

Friday, June 3, 2011 12:09 AM
• Working backwards:

6. The result is supposed to be 727200.

5a. 727200 is 505 days.

5b. 505 days from MinimumDate gives me 3 Adar 5363 (Gregorian: 14 February 1603.)

4. Months between MinimumDate and 2 Adar 5363:  This is where it gets messy.  There is no round number of months.  This should be impossible, meaning that the documentation is either wrong, or Outlook has generated the wrong value for FirstDateTime for my appointment.

4a. But let's give it the benefit of the doubt - 17 months from MinimumDate gives 1 Adar 5363.  So perhaps this was supposed to be 17 months.

3. It is impossible to generate any value here, which will be 17 after performing a modulo 12.

Conclusion: The value 727200 found in my appointment is impossible to obtain by following the outlined steps.  Therefore the steps do not represent what Outlook is actually performing.

Friday, June 3, 2011 12:46 AM
• I am still looking into this issue. I hope to have more information for you soon. Your patience is greatly appreciated.
Josh Curry (jcurry) | Escalation Engineer | US-CSS DSC Protocols Team
Friday, June 3, 2011 2:43 PM
• Any progress on this?

I tried doing the month counting at step 3 using the Gregorian calendar.  It leaves me with a partial month left-over which, if I count this partial month, gives the right result... but it doesn't feel like this is the right solution either.

Thursday, June 16, 2011 12:16 AM
• Hi, I should have something for you soon. As you know, this is a very complex issue.

Josh Curry (jcurry) | Escalation Engineer | US-CSS DSC Protocols Team
Friday, June 17, 2011 4:12 PM
• I am still looking into this issue. I hope to have more information for you soon. Your patience is greatly appreciated.

Josh Curry (jcurry) | Escalation Engineer | US-CSS DSC Protocols Team
Thursday, July 7, 2011 8:49 PM
• Hi, here is the correct way to calculate the FirstDateTime value when the calendar type is CAL_HEBREW.

1. Find the first day of the month of StartDate.
1. Original day = 5/22/2011
1. First day of month of StartDate = 5/1/2011

1. Determine MinimumDate.
1. The document provides the answer for this one, its 9/27/1601.

1. Calculate the number of calendar months between midnight of the days calculated in step 1 and step 2.
1. How many years have passed? 2011 - 1601 = 410.
2. Multiplied by 12 months in a year. 410 * 12 = 4920.
1. Plus how many months until the month the appointment occurs in this year which is the (month number - 1). 4920 + (5 - 1) = 4924
1. Subtract how many months passed in 1601 before our start date which is the (month number - 1). 4924 - (9 - 1) = 4916

1. Take that value modulo Period.
1. 4916 MOD 12 = 8

1. Add that number of months to the MinimumDate, as determined in step 2.
1. This is the tricky part. You can't actually just add 8 to the month value, that would give you 5/27/1602. The correct way to do this is to take the value from step 4 and multiply that by the number of days in a lunar month (29.53). Drop the fractional value and you get 236 days. Add 236 days to 9/27/1601 and you get the date 5/21/1602.

1. Calculate the number of minutes between midnight that day and midnight, January 1, 1601.
1. There are 505 days between Jan 1, 1601 and May 21, 1602
1. 505 * 1440 = 727200

Please let me know if you have any questions.

Josh Curry (jcurry) | Escalation Engineer | US-CSS DSC Protocols Team
Friday, July 15, 2011 2:29 PM
• Thanks!

This magic number is interesting.  I am guessing it works with any lunar calendar, but I'll have to be careful and test more values to see if it works for all of them.

Monday, August 8, 2011 10:46 PM