Monday, 3 October 2011

Programmatically Copying a Recurring Event From One Calendar to Another

I have been spending the last while trying to figure out a problem I had with an event handler I had configured for a calendar. The idea is that if you add an event to a calendar where the event handler is switched on, it bubbles up to a parent calendar which contains all the events. There can be many sub-calendars, only one main one.

So, I created my event handler just as I have described in previous entries. Create an event on the child, bubbles up to the parent. Create a common key between the two. Add an ItemUpdated event handler so then if the child event is updated the parent is updated too. Same with ItemDeleted. Everything hunky dory -

OK, wait, back up the truck. What about recurring events? How do they work?

Leaving them out was not an option. The department had quite a few weekly meeting events. So I hit Mr Google and got a handy link from MSDN here which suggested the following code.

SPListItem recEvent = listItems.Add();

string recData = "<recurrence><rule>" + 
    "<firstDayOfWeek>su</firstDayOfWeek>" +
    "<repeat><daily dayFrequency='1' /></repeat>" +
    "<repeatInstances>5</repeatInstances></rule></recurrence>";

recEvent["Title"] = evtTitle;
recEvent["RecurrenceData"] = recData;
recEvent["EventType"] = 1;
recEvent["EventDate"] = new DateTime(2011,8,15,8,0,0);
recEvent["EndDate"] = new DateTime(2011,9,25,9,0,0);
recEvent["UID"] = System.Guid.NewGuid();
recEvent["TimeZone"] = 13;
recEvent["Recurrence"] = -1;
recEvent.Update();
Now obviously I need to change the line where they build the RecurrenceData property because this will depend on whatever that is for the original object. So the line to use will be

item["RecurrenceData"] = properties.ListItem["RecurrenceData"];

and for the same reason I don't need to code the UID. So I save it and all is well. Then I a dd the recurring event. Let's say I set the start time at 1pm, the end time at 2pm, set it to occur weekly on a Tuesday for two weeks, and save it. Then I go to my parent calendar. In spite of the MSDN advice, still no sign of the recurring event being copied over.

So, I turn on the error handler and get back this error: Value does not fall within accepted range. This error means that the field RecurrenceData does not exist on the list. But how can it not exist? The Recurrence field exists for every event, surely?

Ah - but they are only visible on the form if you click the little Recurring Event checkbox! So if they are not visible on the form, some little quirk means that they are not visible in the code. This is probably because one recurring event contains many child events. So after much searching around I found this link which explained that I had to set the ShowInEdit property of the field concerned to true:


item.Fields["Recurrence"].ShowInEditForm = true;

The only thing that did not work for me in that link was the use of the GetInternalFieldName method. I replaced that with the GetField method, used those lines of code before updating each recurrence data field - you need to do it for all of them and don't forget to call this.DisableEventFiring() after each update! - and then built it once more. Hey presto, it created an event - of sorts.

The problem I have now is that the event that gets created on the parent calendar is a long, unspecified blob that covers all the days of the recurrence time. In order to have it work properly, I had to go into the event from the UI and click "Edit Item". The recurrence info is all there, present and correct. Click Ok, click past the warning message, and it's fine. All I need to do is mimic that update in code. All ideas and suggestions welcome!

ETA. See the following solution kindly provided by Noelle Marchbanks

Add to your copy code:
recEvent["EventType"] = properties.ListItem["EventType"];
recEvent["UID"] = System.Guid.NewGuid();

Even though you are copying an event, these fields are necessary to make it realize that a Recurrence is appropriate. It is IMPERATIVE that you set the EventType to the same as the copied list item, and not hard-code it as 1 as it will break non-recurring events and create orphaned exceptions to the rule.

If, for some reason, you do get orphaned recurring event children (e.g. ID = 1.1.x instead of ID = 1), open up powershell, get the item as an object, set its EventType to 1, then delete it from the list.

Hope this helps.

5 comments:

  1. Hi

    I am having same problem you mentioned above " event that gets created on the parent calendar is a long, unspecified blob that covers all the days of the recurrence time". did you find solution for this ?

    Thanks

    ReplyDelete
  2. I'm afraid I didn't, Simardeep! Sorry. If anyone else figures it out, please let us know.

    Unfortunately I won't have time to revisit this in the near future, but if I find out myself I will certainly update.

    Apologies once again.

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. Add to your copy code:
    recEvent["EventType"] = properties.ListItem["EventType"];
    recEvent["UID"] = System.Guid.NewGuid();

    Even though you are copying an event, these fields are necessary to make it realize that a Recurrence is appropriate. It is IMPERATIVE that you set the EventType to the same as the copied list item, and not hard-code it as 1 as it will break non-recurring events and create orphaned exceptions to the rule.

    If, for some reason, you do get orphaned recurring event children (e.g. ID = 1.1.x instead of ID = 1), open up powershell, get the item as an object, set its EventType to 1, then delete it from the list.

    Hope this helps.

    ReplyDelete
  5. Thank you very much, Noelle and welcome to the blog (which I shamefully haven't updated for a while)! I appreciate that update and will add the text of your comment into the main entry in the hope that anybody else who finds this will have their problem solved. Thanks again for your help.

    ReplyDelete