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

Post to Twitter

Sid Boswell Blog, Work ,

  1. cresh
    March 4th, 2009 at 11:05 | #1

    Very nice and impressive script, thanks a lot! I’d like to have it work in the opposite direction as well, though. So, consider yourself bugged. ;-)

  2. March 4th, 2009 at 17:38 | #2

    I’ll take a swag at it this weekend if I can finish my taxes….

  3. bryan
    March 9th, 2009 at 17:23 | #3

    I would love it to sync back! :)

  4. March 18th, 2009 at 16:56 | #4

    That’s pretty clever. How do you like Leopard Server? I love OS X for all my desktop stuff, Im curious enough to try it server side I think.

  5. March 21st, 2009 at 10:57 | #5

    Leopard Server is pretty slick. Like most Apple products it’s designed around useability. We have it running on an old Quicksilver desktop. We are a pretty small company and use it for individual calendaring, group calendaring, room reservations, hosting an internal wiki, and VPN. Prior to the Leopard Server we ran a doku wiki and a mediawiki on an Ubuntu server (running on an old iMac blueberry) and used a VPN switch from DLink. We really like the wiki and even our tech neophytes are very comfortable using and editing entries, it was just sort of a bonus as we were dying for a calendaring application.

  6. March 31st, 2009 at 05:05 | #6

    I can’t wait for Snow Leopard Server ( probably at WWDC 2009 ), rumors say that there will be native support on iCal Server 2 ( new caldav specs) for both internal iCal’s and iPhones full sync over the cloud and loads of new features and performance improvements! Stay tuned in June :)

  7. March 31st, 2009 at 08:40 | #7

    We are using an old Quicksilver machine for our Leopard Silver so no Slow Leopard for us. We are considering investing in a mini to act as a server if Snow Leopard is worth the upgrade.

  8. Todd Brannam
    June 22nd, 2009 at 17:05 | #8

    For some reason recurring events don’t seem to get copied – or is it just me?

  9. A McKee
    July 15th, 2009 at 06:48 | #9

    @Todd Brannam Indeed, recurring events are not copied. Thanks for the code, though – it’s quite helpful! If only recurring events could be copied as well. *hint hint*

  10. July 15th, 2009 at 14:01 | #10

    Great script. Thanks.

  11. July 31st, 2009 at 16:08 | #11

    @Todd Brannam

    @A McKee
    I don’t seem to have a problem with recurring events. I wonder why mine work just fine.

    I have also been meaning to make this bi-directional, but got sucked up in an iPhone development course from iTunes U. Like my blog, I’ll get around to it eventually….

  12. October 23rd, 2009 at 15:54 | #12

    Since iPhone 3.1 you no longer need to run this script. “Subscribed” to calendars now sync via iTunes (but not Mobile Me). So you just need to enable your “subscribed” to calendars in the iTunes Info tab and unselect those that are synching via Mobile me so you don’t have dups. Then your local calendars sync automagically and your remote calendars sync every time you sync to iTunes.

  13. Jamelle Adnan
    June 15th, 2010 at 19:05 | #13

    When trying to get my ical subscription (google calendar) to synch with mobile me, i’ve run into this error with the above script:

    error “iCal got an error: Can’t make missing value into type text.” number -1700 from missing value to text

    at this line:

    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

    Any help with this would be greatly appreciated.

    Jamelle

  1. No trackbacks yet.
Get Adobe Flash playerPlugin by wpburn.com wordpress themes