Home > Blog, Work > iCal Server (CalDAV) to Mobile Me and my iPhone

iCal Server (CalDAV) to Mobile Me and my iPhone

February 20th, 2009

apple_iphone_ipod_cameraI can’t take all the credit for this.  I can’t even take a small amount of credit for this.  All I did was google, google, and more google for a method to get my calendar entries from my company calendar server (running Leopard Server) to my iPhone. Most of the solutions I came across involved some sort of sync between two calendars (local and remote) using paid for software (which may provide more functionality (like reverse sync)) or using paid software to sync your calendars to Google Calendar and use that as your iPhone calendar.

What I really wanted was alerts. I can’t tell you how many times I missed a conference call or was late to a meeting that was on my company calendar that wasn’t on my iPhone. I just wanted it to ping me 5-15 mintues before a meeting, is this too much to ask for?

The background of my situation is that I have a laptop (so not something that is always on), use Mobile Me for address book and personal calendars synchronization and I wanted to have access to my company calendar.  You would think, since I have Mobile Me running on my laptop that I would be able to see my calendar entries on my iPhone. But for some reason that I still haven’t been able to find out, that’s not the case. You would also think that since I’m running an OS X laptop (Apple), an OS X Server (Apple), Mobile Me (Apple) and an iPhone (guess who) that it would be out-of-the box functionality. There are lots of rumors that that functionality is coming in Snow Leopard (OS X 10.7), but we’re running our server on an old Quicksilver (PPC) which won’t run Snow Leopard (Snow Leopard will be Intel processors only) so there was no relief coming.

What came across is a script that creates a duplicate of your server based calendar to a local calendar. Mobile Me picks up that local calendar and keeps it in sync with my iPhone. I made several changes to include the alarms (which the initial script didn’t get). I’ve pasted the script below.

To use it:

  1. copy all the text and paste it into the Script Editor (in Applications -> Apple Script).
  2. Change the names of the two calendars to the name of the server calendar and a new .local or .copy entry to be your local one (the first time you run the script the local one will be created).
  3. Save it as an application and run it every time you want to sync your CalDAV calendar to your local calendar.
  4. Uncheck the .local or .copy calendar in iCal so you won’t see duplicate entries. One drawback which I can live with is that on my laptop I get two alarms simutaneously.
  5. If you don’t like the dialog box at the end just comment the last line
    out.
  6. If you wanted this to run periodically, you can create a crontab
    or run it via the scheduler, or even run it as a calendar entry itself.

If I really wanted to, I bet I could get the reverse sync working so that entries created or modified on the iPhone would sync to the CalDAV calendar, but I just haven’t wanted to go there yet. Bug me and I may work on the reverse.

(* 
Script to duplicate Calendar orgCalendar into target dupCalendar
E.H. 12.9.2008
S.B. 02.20.2009 (my birthday :) )
*)

property myCopies : 0
property myUpdates : 0
property myObsoletes : 0
property orgCalendar : "Server Calendar [PUT NAME OF CALENDAR HERE]"
property dupCalendar : "[PUT NAME OF TARGET CALENDAR HERE].local"
property dupEvents : {}
property myDeletes : {}

set myCopies to 0
set myUpdates to 0
set myObsoletes to 0
set dupEvents to {}

tell application "iCal"

	-- set theCalendars to every calendar
	set theCalendarNames to title of every calendar
	set theOrgCalendar to a reference to calendar orgCalendar

	if theCalendarNames contains dupCalendar then
		set theCalendar to a reference to calendar dupCalendar
	else
		set theCalendar to make new calendar with properties {title:dupCalendar}
		--set theCalendar to make new calendar with properties {title:dupCalendar, color:"{65535, 0, 0}"}
	end if

	set the eventList to uid of every event of theOrgCalendar as list
	set the eventCount to the count of the eventList

	repeat with theUId in eventList
		tell theOrgCalendar
			set theEvent to (the first event whose uid is theUId)
			-- set theProperties to the properties of theEvent as record
			set theDate to the start date of theEvent
			set theSummary to the summary of theEvent
			set theStampDate to the stamp date of theEvent
		end tell

		tell theCalendar
			try
				set theOldEvent to (the first event of theCalendar whose (start date) is theDate as date)
				set similar_found to true
			on error
				set similar_found to false
				set theEndDate to the end date of theEvent
				set theAllDay to the allday event of theEvent
				set theLocation to the location of theEvent
				-- Funny construction to work araund the fact that location may be missing a value
				try
					if theLocation is equal to "" then
					end if
				on error
					set theLocation to ""
				end try
				set theDescription to the description of theEvent
				try
					if theDescription is equal to "" then
					end if
				on error
					set theDescription to ""
				end try
				if theAllDay is true then -- work around a funny bug with all day events
					set theDate to (theDate as date) + 2 * hours
					set theEndDate to (theEndDate as date) + 2 * hours
				end if
				set newEvent to make new event at end with properties {summary:theSummary, location:theLocation, start date:theDate, end date:theEndDate, allday event:theAllDay, description:theDescription}
				-- make new event at end with properties theProperties

				-- sid's effort

				set sidCount to 1
				try
					repeat
						set theAlarm to item sidCount of sound alarm of theEvent
						set alarmTime to (trigger interval of theAlarm)
						set alarmSound to (sound name of theAlarm)
						set newAlarm to make new sound alarm at end of sound alarms of newEvent with properties {trigger interval:alarmTime, sound name:alarmSound}
						set sidCount to (sidCount + 1)
					end repeat
				end try

				-- end sid's effort

				set the end of dupEvents to (the uid of newEvent)

				set myCopies to (myCopies + 1)
			end try
		end tell

		set second_necessary to false
		if similar_found is true then
			set theOldSummary to the summary of theOldEvent
			if theSummary is not equal to theOldSummary then
				--is there a different one?
				try
					set theOldEvent1 to (the second event of theCalendar whose (start date) is theDate as date)
					set theOldSummary to the summary of theOldEvent1
					if theSummary is equal to theOldSummary then
						set theOldEvent to theOldEvent1
						set the end of dupEvents to (the uid of theOldEvent)
					else
						-- cycle repeat ?
					end if
				on error
					-- beep
					try
						set theEvent1 to (the second event of theOrgCalendar whose (start date) is theDate as date)
						set second_necessary to true
					on error
						set the end of dupEvents to (the uid of theOldEvent)
					end try
				end try
			else
				set the end of dupEvents to (the uid of theOldEvent)
			end if

			if second_necessary is true then
				set theEndDate to the end date of theEvent
				tell theCalendar
					set theOldEvent to make new event at end with properties {summary:theSummary, start date:theDate, end date:theEndDate}
					-- sid's effort

					set sidCount to 1
					try
						repeat
							set theAlarm to item sidCount of sound alarm of theEvent
							set alarmTime to (trigger interval of theAlarm)
							set alarmSound to (sound name of theAlarm)
							set newAlarm to make new sound alarm at end of sound alarms of newEvent with properties {trigger interval:alarmTime, sound name:alarmSound}
							set sidCount to (sidCount + 1)
						end repeat
					end try

					-- end sid's effort

				end tell
				set the end of dupEvents to (the uid of theOldEvent)
			end if

			set theOldStampDate to the stamp date of theOldEvent
			if theStampDate is greater than theOldStampDate then
				-- update the event
				set summary of theOldEvent to theSummary -- capitalization may have changed
				set theAllDay to the allday event of theEvent
				set allday event of theOldEvent to theAllDay
				set theEndDate to the end date of theEvent
				if theAllDay is true then -- work around a funny bug with all day events
					set theEndDate to (theEndDate as date) + 2 * hours
				end if
				set end date of theOldEvent to theEndDate
				set theDescription to the description of theEvent
				try
					if theDescription is equal to "" then
					end if
				on error
					set theDescription to ""
				end try
				set description of theOldEvent to theDescription

				-- sid's effort

				set sidCount to 1
				try
					repeat
						set theAlarm to item sidCount of sound alarm of theEvent
						set alarmTime to (trigger interval of theAlarm)
						set alarmSound to (sound name of theAlarm)
						-- add new alarm?
						try
							set oldAlarm to item sidCount of sound alarm of theOldEvent
							set oldTime to (trigger interval of oldAlarm)
							set oldAlarmSound to (sound name of oldAlarm)
							if oldTime is not equal to alarmTime then
								set trigger interval of oldAlarm to alarmTime
							end if
							if oldAlarmSound is not equal to alarmSound then
								set sound name of oldAlarm to alarmSound
							end if
							set sidCount to (sidCount + 1)
						on error
							set newAlarm to make new sound alarm at end of sound alarms of newEvent with properties {trigger interval:alarmTime, sound name:alarmSound}
							set sidCount to (sidCount + 1)
						end try
					end repeat
				on error
					--delete alarm?
					try
						repeat
							set oldAlarm to item sidCount of sound alarm of theOldEvent
							set oldTime to (trigger interval of oldAlarm)
							delete oldAlarm
							set sidCount to (sidCount + 1)
						end repeat
					end try
				end try

				-- end sid's effort

				set myUpdates to myUpdates + 1
			end if
		end if

	end repeat
end tell

-- Delete obsolete events

set myObsoletes to 0
set myDeletes to {}

tell application "iCal"
	set myUIDs to uid of events of theCalendar
end tell

repeat with myUID in myUIDs
	if dupEvents does not contain myUID then
		set the end of myDeletes to myUID
		set myObsoletes to (myObsoletes + 1)
	end if
end repeat

tell application "iCal"
	repeat with myDel in myDeletes
		delete (every event of theCalendar whose uid is myDel)
	end repeat
end tell

-- delete duplicates

set myDeletes to {}

tell application "iCal"
	set myStarts to start date of events of theCalendar
	set mySummaries to summary of events of theCalendar
	set myUIDs to uid of events of theCalendar
	set myLength to length of myUIDs
end tell

repeat with i from 1 to (myLength - 1)
	set thisStart to (item i of myStarts)
	set thisSumm to (item i of mySummaries)
	repeat with j from (i + 1) to myLength
		set thatStart to (item j of myStarts)
		set thatSumm to (item j of mySummaries)
		if thisSumm is equal to thatSumm and thisStart is equal to thatStart then
			set the end of myDeletes to (item j of myUIDs)
			exit repeat
		end if
	end repeat
end repeat

set n to count of myDeletes

tell application "iCal"

	repeat with myDel in myDeletes
		delete (every event of theCalendar whose uid is myDel)
	end repeat

	-- set the visible of calendar theCalendar to false

end tell

display dialog (myCopies & " records duplicated, " & myUpdates & " records updated and " & myObsoletes & " obsolete ones deleted") as text

Blog, Work ,

Get Adobe Flash player