$theTitle=wp_title(" - ", false); if($theTitle != "") { ?>
About Virtualization, VDI, SBC, Application Compatibility and anything else I feel like
Again an old war story, this time about timezone handling in Outlook/Exchange.
I am not sure which year it was but I had just started to work for a new company and inherited an Exchange 5.5 Server.
The mail had been migrated from an earlier version and calendar data was migrated from Schedule+.
On the first change to Daylight Savings (DTS) all recurring appointments where shown one hour later (or earlier can’t remember) in Outlook. A manual change was not an option: there were over 2000 mailboxes each with a lot of appointments.
We first tried a workaround by disabling DTS on the the workstations and then manually change the time when changing from and to DTS.
But this influenced the timestamps on externals mails and of course appointments with external parties.
After a lot of (and I really mean a lot) of researching I found that Outlook stores all times in an appointment as relative (UTC) time.
Upon display it uses an undocumented TimeZone descriptor field to convert to Local Time.
This Timezone information is in a Named Property of the Appointment that can only be read using Extended MAPI.
I found the property (Named Property 0x8233) with OutlookSpy (absolutely brilliant tool btw) and it’s of type PT_BINARY (array of Byte).
In my code I placed the following note to describe the format (with the correct data for the Dutch TimeZone):
When I was writing this article today, I looked at this value again with Outlook Spy (using Outlook 2010 with Exchange 2010).
I was pretty surprised that OutlookSpy now knows this property:
And I even found that Microsoft has actually documented this property now as PidLidTimeZoneStruct which looks like this:
1 2 3 4 5 6 7 | long lBias; // offset from GMT long lStandardBias; // offset from bias during standard time long lDaylightBias; // offset from bias during daylight time WORD wStandardYear; // matches the stStandardDate's wYear member SYSTEMTIME stStandardDate; // time to switch to standard time WORD wDaylightYear; // matches the stDaylightDate's wYear field SYSTEMTIME stDaylightDate; // time to switch to daylight time |
I also found it in the Outlook 2007 docs as dispidTimeZoneStruct but the wStandardYear and wDaylighYear are noted as Reserved there:
1 2 3 4 5 6 7 | long lBias; // offset from GMT long lStandardBias; // offset from bias during standard time long lDaylightBias; // offset from bias during daylight time WORD wReserved1; // reserved SYSTEMTIME stStandardDate; // time to switch to standard time WORD wReserved2; // reserved SYSTEMTIME stDaylightDate; // time to switch to daylight time |
But on with the story: I wrote a tool in Delphi 6 that enumerated the Global Address List and opened each mailbox on the server.
From each mailbox I opened the calendar folder and from that folder enumerated all Appointments.
For each Appointment I needed to check if it was recurring (only recurrring items have this property) and if so I updated the property with the proper value (that I found by creating a new recurring appointment).
I found back a part of the code because I posted it to the MAPI Developers group in 2006. Sadly I don’t have the complete code anymore but this is the interesting part:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | const TZStr: String= 'C4FFFFFF00000000C4FFFFFF000000000A00000005000300000000000000000000000300000005000200000000000000'; // Appointment is recurring? if Appt.IsRecurring then begin Msg:=Appt.MAPIOBJECT as IMessage; with Prop[0] do begin lpguid:=@propGUID; ulKind:=MNID_ID; Kind.lID:=$8233; // Found using OutlookSpy end; Names[0]:=@Prop[0]; if (S_OK = Msg.GetIDsFromNames(1, Names[0], MAPI_CREATE, PSPropTagArray(pTag))) then begin PR_YOUR_PROP:=pTag^.aulPropTag[0] or PT_BINARY ; //is it PT_BINARY? if S_OK = HrGetOneProp(Msg, PR_YOUR_PROP, pProp) then begin // pProp.Value.bin.lpb now points to the data. pProp.Value.bin.cb is its length HexStrToBytes(TZStr, pProp.Value.bin.lpb); // Modify the property if S_OK = HrSetOneProp(Msg, pProp) then begin Msg.SaveChanges(KEEP_OPEN_READWRITE); end; end; end; end; |
The tool was started on a Friday evening and ran till Sunday night but was (luckily) finished on time before the next working day.
The Monday was a little exciting but all appointments were processed 100% good and all was ok!
Leave a reply