Recurring events

HI–

I have just succeeded in selecting a years’ worth of events – including recurring events – from an iCal-maintained calendar (without actually using the iCal app) and I thought I’d share the code here in case it might help someone else.

I keep about a dozen calendars in the system calendar data base maintained by my iCal application. The code below goes directly to the system calendar data base, selects one calendar out of the dozen, then makes a list of all the events on that calendar for the next year.

I had tried to do this using ‘tell application “iCal”’ AppleScript commands, but recurring events would not come out properly. I tried following Nigel Garvey’s technique to parse the recurrence string, but I never got it working with confidence.

Then I found out that Cocoa has the exact interface I was looking for: (http://developer.apple.com/library/mac/#documentation/AppleApplications/Conceptual/CalendarStoreProgGuide). As recurrence is so complicated, I imagine there might be some recurrence strings that even Apple’s code cannot parse correctly, but up with that restriction I am for the present willing to put.

All (!) I had to do was learn to use XCode (http://developer.apple.com/technologies/tools/xcode.html), study the notation for AppleScriptObjC, (http://developer.apple.com/library/mac/#releasenotes/ScriptingAutomation/RN-AppleScriptObjC/index.html), learn to read Cocoa documentation (http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CocoaFundamentals/Introduction/Introduction.html) and absorb a lot of wisdom from the good people on this board.

After I initiated my XCode project, I had to add the CalendarStore.framework to “Linked Frameworks” and " #import <CalendarStore/CalendarStore.h>" to “Prefix.pch”. Then I used Help → Developer Documentation (option-command-?) to look up each Cocoa class involved and Run → Console to see my ‘log’ statements come out. It was tedious, but fun.

One extra bonus of this technique is that the events appear (so far) to come out in “event start date” order. Using iCal, they didn’t.

Here’s the code:

I left in all my log commands so that you could see the intermediate steps as I did.

	on applicationWillFinishLaunching_(aNotification)
		-- Insert code here to initialize your application before any files are opened 
		log "applicationWillFinishLaunching_"
		my MakeMCCVhtml()
		quit
	end applicationWillFinishLaunching_
	
	on MakeMCCVhtml()
		
		tell class "NSDate" of current application
			set TimeNow to |date|()
			set YearFromNow to ¬
				dateWithTimeIntervalSinceNow_(60 * 60 * 24 * 365)
		end tell
		log "TimeNow=" & (description of TimeNow)
		log "YearFromNow=" & (description of YearFromNow)
		
		tell class "CalCalendarStore" of current application to ¬
			set SharedCalendarStore to defaultCalendarStore()
		tell SharedCalendarStore to ¬
			set MyCalendars to calendars()
		
		repeat with aCalendar in MyCalendars
			if ((type of aCalendar as text) is "CalDAV" and ¬
				(|title| of aCalendar as text) is "MCCV Congregational Events") then
				set CECalendar to aCalendar
				exit repeat
			end if
		end repeat
		
		tell class "NSArray" of current application to ¬
			set CECalendarInArray to arrayWithObject_(CECalendar)
		
		tell class "CalCalendarStore" of current application to ¬
			set NextYearPredicate to ¬
				eventPredicateWithStartDate_endDate_calendars_(TimeNow, YearFromNow, CECalendarInArray)
		tell SharedCalendarStore to ¬
			set NextYearsEvents to eventsWithPredicate_(NextYearPredicate)
		
		log "class of SharedCalendarStore=" & ¬
			|description|() of (|class|() of SharedCalendarStore)
		log "class of MyCalendars=" & ¬
			|description|() of (|class|() of MyCalendars)
		log "class of NextYearPredicate=" & ¬
			|description|() of (|class|() of NextYearPredicate)
		log "class of NextYearsEvents=" & ¬
			|description|() of (|class|() of NextYearsEvents)
		log "count of NextYearsEvents=" & ¬
			(count of NextYearsEvents)
		
		set anEvent to item 1 of NextYearsEvents
		
		log "class of anEvent=" & ¬
			|description|() of (|class|() of anEvent)
		log "description of anEvent=" & ¬
			|description|() of anEvent
		log "dateStamp  of anEvent=" & ¬
			|description|() of (|dateStamp|() of anEvent)
		log "occurrence of anEvent=" & ¬
			|description|() of (occurrence() of anEvent)
		log "startDate  of anEvent=" & ¬
			|description|() of (|startDate|() of anEvent)
		log "endDate    of anEvent=" & ¬
			|description|() of (|endDate|() of anEvent)
		log "title of anEvent=" & ¬
			|title|() of anEvent
		log "notes of anEvent=" & ¬
			notes() of anEvent
		log "isAllDay of anEvent=" & ¬
			(|isAllDay|() of anEvent)
		log "recurrenceRule of anEvent=" & ¬
			(|recurrenceRule|() of anEvent)
		
		set PreviousStartTime to (description of (|startDate|() of anEvent) as text)
		
		repeat with anEvent in NextYearsEvents
			set EVStartTime to (description of (|startDate|() of anEvent) as text)
			if EVStartTime comes before PreviousStartTime then
				set OrderFlag to "***"
			else
				set OrderFlag to "   "
			end if
			set PreviousStartTime to EVStartTime
			log OrderFlag & " " & EVStartTime & ": " & ¬
				(|title|() of anEvent)
			if (|recurrenceRule|() of anEvent) is missing value then
			else
				log "recurrenceRule=" & ¬
					(description of (|recurrenceRule|() of anEvent))
			end if
		end repeat
		
		log "Done."
		return
	end MakeMCCVhtml

“Build and Run” produced the following Console output:

I’d welcome your comments.

–Gil