Sync subscribed iCal calendars to MobileMe via script

Sep 17, '08 07:30:03AM

Contributed by: Anonymous

As discussed in many places, the MobileMe cloud does not serve calendars you have subscribed to out to the iPhone -- only local calendars are synced. I need to see a calendar which my secretary maintains, so I created an AppleScript which duplicates that calendar into one local to my Mac, which then syncs to the iPhone.

My AppleScript skills are rudimentary, but here it is:

(* 
Script to duplicate Calendar orgCalendar into target dupCalendar
E.H. 12.9.2008
*)

property myCopies : 0
property myUpdates : 0
property myObsoletes : 0
property orgCalendar : "Sekretariat"
property dupCalendar : "Sekretariat copy"
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
                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}
                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
                
                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
To adopt this to your own needs, simply replace the names associated with orgCalendar (the original calendar) and dupCalendar (the duplicate the script will create) near the top of the script.

[robg adds: I tested this, and it works as described. This previous hint describes a manual solution to the problem, and the comments offer some script alternatives. This version, however, seems more robust and makes updating easier.

The advantage of using a script is that you can then use iCal (or Automator) to set up an event to run the script automatically, keeping your "fake" subscribed calendar up to date. I did just that with this script, setting up a repeating event in iCal to run the script once a week. If you're going to do this, you may want to comment out the final display dialog line, so that you don't have a window popping up on your screen each week.]

Comments (13)


Mac OS X Hints
http://hints.macworld.com/article.php?story=20080914052704468