|
tell application "Calendar" |
|
-- Define the source and destination calendars |
|
set sourceCalendar to calendar "SourceCalendar" |
|
set destCalendar to calendar "DestinationCalendar" |
|
|
|
-- Set the log file path |
|
set logFile to (path to desktop as text) & "calendar_copy_log.txt" |
|
|
|
-- Script version |
|
set scriptVersion to "1.4.1" |
|
|
|
-- Enable or disable logging |
|
set enableLogging to true |
|
|
|
-- Enable or disable alerts (alarms) |
|
set enableAlerts to true |
|
|
|
-- Set the default alarm trigger (minutes before event; negative value) |
|
set defaultAlarmMinutes to -15 |
|
|
|
-- Set date range (today to 1 year ahead) |
|
set currentDate to current date |
|
set startOfToday to currentDate - (time of currentDate) -- Midnight today |
|
set endOfRange to currentDate + (365 * days) |
|
|
|
-- Log script start with version |
|
if enableLogging then |
|
set logMsg to quoted form of (((current date) as text) & " - Starting calendar copy script (Version " & scriptVersion & ")") |
|
do shell script "echo " & logMsg & " >> " & quoted form of POSIX path of logFile |
|
end if |
|
|
|
-- Check if this is the first run (destination calendar is empty) |
|
set destEvents to events of destCalendar |
|
set isFirstRun to ((count of destEvents) = 0) |
|
if enableLogging then |
|
set logMsg to quoted form of (((current date) as text) & " - First run: " & isFirstRun) |
|
do shell script "echo " & logMsg & " >> " & quoted form of POSIX path of logFile |
|
end if |
|
|
|
-- Fetch source events based on run type |
|
if isFirstRun then |
|
set allSourceEvents to events of sourceCalendar |
|
if enableLogging then |
|
set logMsg to quoted form of (((current date) as text) & " - Fetching all events for first run") |
|
do shell script "echo " & logMsg & " >> " & quoted form of POSIX path of logFile |
|
end if |
|
else |
|
set allSourceEvents to (events of sourceCalendar whose start date ≥ startOfToday) |
|
if enableLogging then |
|
set logMsg to quoted form of (((current date) as text) & " - Fetching events starting today or later") |
|
do shell script "echo " & logMsg & " >> " & quoted form of POSIX path of logFile |
|
end if |
|
end if |
|
|
|
-- Filter events to include those with occurrences in the date range |
|
set sourceEvents to {} |
|
repeat with srcEvent in allSourceEvents |
|
set eventStart to start date of srcEvent |
|
set isRecurring to (recurrence of srcEvent is not missing value) |
|
|
|
if isRecurring then |
|
set recurrenceRule to recurrence of srcEvent |
|
if my eventOccursInRange(eventStart, recurrenceRule, startOfToday, endOfRange) then |
|
set end of sourceEvents to srcEvent |
|
end if |
|
else if (eventStart ≥ startOfToday and eventStart ≤ endOfRange) then |
|
set end of sourceEvents to srcEvent |
|
end if |
|
end repeat |
|
|
|
-- Build a deduplicated list of source UIDs |
|
set sourceUIDs to {} |
|
repeat with srcEvent in sourceEvents |
|
set eventUID to uid of srcEvent |
|
if eventUID is not in sourceUIDs then |
|
set end of sourceUIDs to eventUID |
|
end if |
|
end repeat |
|
if enableLogging then |
|
set logMsg to quoted form of (((current date) as text) & " - Source UIDs: " & (my joinList(sourceUIDs, ", "))) |
|
do shell script "echo " & logMsg & " >> " & quoted form of POSIX path of logFile |
|
end if |
|
|
|
-- Copy events |
|
repeat with sourceEvent in sourceEvents |
|
try |
|
set eventSummary to summary of sourceEvent |
|
set eventStartDate to start date of sourceEvent |
|
set eventEndDate to end date of sourceEvent |
|
set eventUID to uid of sourceEvent |
|
set copiedMarker to "copied-from:" & eventUID |
|
set isRecurring to (recurrence of sourceEvent is not missing value) |
|
|
|
if enableLogging then |
|
set logMsg to quoted form of (((current date) as text) & " - Processing event: " & eventSummary & " (UID: " & eventUID & ")") |
|
do shell script "echo " & logMsg & " >> " & quoted form of POSIX path of logFile |
|
end if |
|
|
|
-- Check for existing events with this UID |
|
set existingEvents to (events of destCalendar whose url is copiedMarker) |
|
if (count of existingEvents) > 0 then |
|
-- Check if the existing event's date matches the source |
|
set existingEvent to first item of existingEvents |
|
set existingStartDate to start date of existingEvent |
|
if existingStartDate is not equal to eventStartDate then |
|
-- Date changed; delete the old event and recopy |
|
if enableLogging then |
|
set logMsg to quoted form of (((current date) as text) & " - Date changed from " & existingStartDate & " to " & eventStartDate & "; deleting old event") |
|
do shell script "echo " & logMsg & " >> " & quoted form of POSIX path of logFile |
|
end if |
|
delete existingEvent |
|
set shouldCopy to true |
|
else |
|
-- No change; skip |
|
if enableLogging then |
|
set logMsg to quoted form of (((current date) as text) & " - Skipped (already copied, date unchanged)") |
|
do shell script "echo " & logMsg & " >> " & quoted form of POSIX path of logFile |
|
end if |
|
set shouldCopy to false |
|
end if |
|
else |
|
-- No existing event; copy it |
|
set shouldCopy to true |
|
end if |
|
|
|
if shouldCopy then |
|
set eventProps to {summary:eventSummary, start date:eventStartDate, end date:eventEndDate, url:copiedMarker} |
|
|
|
try |
|
if description of sourceEvent is not missing value then |
|
set description of eventProps to description of sourceEvent |
|
end if |
|
end try |
|
|
|
tell destCalendar |
|
set newEvent to make new event with properties eventProps |
|
end tell |
|
|
|
if allday event of sourceEvent then |
|
set allday event of newEvent to true |
|
if enableLogging then |
|
set logMsg to quoted form of (((current date) as text) & " - Event is all-day") |
|
do shell script "echo " & logMsg & " >> " & quoted form of POSIX path of logFile |
|
end if |
|
end if |
|
|
|
if isRecurring then |
|
try |
|
set recurrence of newEvent to recurrence of sourceEvent |
|
if enableLogging then |
|
set logMsg to quoted form of (((current date) as text) & " - Recurrence copied successfully") |
|
do shell script "echo " & logMsg & " >> " & quoted form of POSIX path of logFile |
|
end if |
|
on error errMsg |
|
if enableLogging then |
|
set logMsg to quoted form of (((current date) as text) & " - Failed to copy recurrence: " & errMsg) |
|
do shell script "echo " & logMsg & " >> " & quoted form of POSIX path of logFile |
|
end if |
|
end try |
|
end if |
|
|
|
if enableAlerts and not (allday event of sourceEvent) then |
|
try |
|
tell newEvent |
|
make new display alarm at end with properties {trigger interval:defaultAlarmMinutes} |
|
end tell |
|
if enableLogging then |
|
set logMsg to quoted form of (((current date) as text) & " - Added alarm " & (-defaultAlarmMinutes) & " minutes before event") |
|
do shell script "echo " & logMsg & " >> " & quoted form of POSIX path of logFile |
|
end if |
|
on error errMsg |
|
if enableLogging then |
|
set logMsg to quoted form of (((current date) as text) & " - Failed to add alarm: " & errMsg) |
|
do shell script "echo " & logMsg & " >> " & quoted form of POSIX path of logFile |
|
end if |
|
end try |
|
else |
|
if enableLogging then |
|
if not enableAlerts then |
|
set logMsg to quoted form of (((current date) as text) & " - Skipped alarm (alerts disabled)") |
|
else |
|
set logMsg to quoted form of (((current date) as text) & " - Skipped alarm for all-day event") |
|
end if |
|
do shell script "echo " & logMsg & " >> " & quoted form of POSIX path of logFile |
|
end if |
|
end if |
|
|
|
if enableLogging then |
|
set logMsg to quoted form of (((current date) as text) & " - Event copied successfully") |
|
do shell script "echo " & logMsg & " >> " & quoted form of POSIX path of logFile |
|
end if |
|
end if |
|
on error errMsg |
|
if enableLogging then |
|
set logMsg to quoted form of (((current date) as text) & " - Error processing event '" & eventSummary & "': " & errMsg) |
|
do shell script "echo " & logMsg & " >> " & quoted form of POSIX path of logFile |
|
end if |
|
end try |
|
end repeat |
|
|
|
-- Delete events from destCalendar not in source |
|
set destEvents to (events of destCalendar whose start date ≥ startOfToday and start date ≤ endOfRange) |
|
repeat with destEvent in destEvents |
|
try |
|
set destURL to url of destEvent |
|
if destURL is not missing value and destURL starts with "copied-from:" then |
|
set origUID to text 13 thru -1 of destURL |
|
if enableLogging then |
|
set logMsg to quoted form of (((current date) as text) & " - Checking dest event: " & (summary of destEvent) & " with URL: " & destURL & " (extracted UID: " & origUID & ")") |
|
do shell script "echo " & logMsg & " >> " & quoted form of POSIX path of logFile |
|
end if |
|
|
|
if origUID is not in sourceUIDs then |
|
if enableLogging then |
|
set logMsg to quoted form of (((current date) as text) & " - Deleting event from destCalendar: " & (summary of destEvent) & " (UID: " & origUID & ")") |
|
do shell script "echo " & logMsg & " >> " & quoted form of POSIX path of logFile |
|
end if |
|
delete destEvent |
|
end if |
|
end if |
|
on error errMsg |
|
if enableLogging then |
|
set logMsg to quoted form of (((current date) as text) & " - Error checking event for deletion: " & errMsg) |
|
do shell script "echo " & logMsg & " >> " & quoted form of POSIX path of logFile |
|
end if |
|
end try |
|
end repeat |
|
|
|
if enableLogging then |
|
set logMsg to quoted form of (((current date) as text) & " - Script completed") |
|
do shell script "echo " & logMsg & " >> " & quoted form of POSIX path of logFile |
|
end if |
|
end tell |
|
|
|
-- Helper to check if a recurring event occurs in the date range |
|
on eventOccursInRange(eventStart, recurrenceRule, rangeStart, rangeEnd) |
|
if eventStart > rangeEnd then return false |
|
|
|
set AppleScript's text item delimiters to ";" |
|
set ruleParts to text items of recurrenceRule |
|
set AppleScript's text item delimiters to "" |
|
|
|
set freq to "" |
|
set interval to 1 |
|
set untilDate to missing value |
|
|
|
repeat with part in ruleParts |
|
if part starts with "FREQ=" then |
|
set freq to text 6 thru -1 of part |
|
else if part starts with "INTERVAL=" then |
|
set interval to (text 10 thru -1 of part) as integer |
|
else if part starts with "UNTIL=" then |
|
set untilStr to text 7 thru -1 of part |
|
set untilYear to text 1 thru 4 of untilStr |
|
set untilMonth to text 5 thru 6 of untilStr |
|
set untilDay to text 7 thru 8 of untilStr |
|
set untilDate to date (untilMonth & "/" & untilDay & "/" & untilYear & " 00:00:00") |
|
end if |
|
end repeat |
|
|
|
if untilDate is not missing value and untilDate < rangeStart then return false |
|
|
|
set currentOccurrence to eventStart |
|
set timeDelta to 0 |
|
if freq is "DAILY" then |
|
set timeDelta to interval * days |
|
else if freq is "WEEKLY" then |
|
set timeDelta to interval * weeks |
|
else if freq is "MONTHLY" then |
|
set timeDelta to interval * 30 * days |
|
else if freq is "YEARLY" then |
|
set timeDelta to interval * 365 * days |
|
end if |
|
|
|
if timeDelta = 0 then return false |
|
|
|
repeat while currentOccurrence ≤ rangeEnd |
|
if currentOccurrence ≥ rangeStart then return true |
|
set currentOccurrence to currentOccurrence + timeDelta |
|
if untilDate is not missing value and currentOccurrence > untilDate then exit repeat |
|
end repeat |
|
|
|
return false |
|
end eventOccursInRange |
|
|
|
-- Helper to join list items with a delimiter |
|
on joinList(theList, delim) |
|
set AppleScript's text item delimiters to delim |
|
set joinedString to theList as text |
|
set AppleScript's text item delimiters to "" |
|
return joinedString |
|
end joinList |
Version 1.3