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
cresh
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. ๐
Sid Boswell
I’ll take a swag at it this weekend if I can finish my taxes….
bryan
I would love it to sync back! ๐
Josh - Vinyl Cutter Expert
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.
Sid Boswell
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.
ygbr
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 ๐
Sid Boswell
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.
Todd Brannam
For some reason recurring events don’t seem to get copied – or is it just me?
A McKee
@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*
Jim Lindsay
Great script. Thanks.
Sid Boswell
@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….
Sid Boswell
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.
Jamelle Adnan
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