iCal Server (CalDAV) to Mobile Me and my iPhone
I 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:
- copy all the text and paste it into the Script Editor (in Applications -> Apple Script).
- 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).
- Save it as an application and run it every time you want to sync your CalDAV calendar to your local calendar.
- 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.
- If you don’t like the dialog box at the end just comment the last line
out. - 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
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.
I’ll take a swag at it this weekend if I can finish my taxes….
I would love it to sync back!
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.
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.
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
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.
For some reason recurring events don’t seem to get copied – or is it just me?
@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*
Great script. Thanks.
@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….
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.
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