Delete old Calendar events

For legal reasons I only need calendar events less than 7 years old.
I created a script to delete Calendar events older than 7 years.
This also will help clean up my iPhone.

It is slow. It takes about 12 seconds to delete each event

Here it is…

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

property myCalendars : {}
property rawEvents : missing value
property calEvents : {}
property oldDate : date ("January 1 " & ((year of (current date)) - 7))

on run
	local cal, cals, myDate, anEvent, c, flag, i, j, anEvent, m
	tell application "Calendar" to set m to name of calendars
	repeat with i from 1 to count m
		if item i of m is not in {"Siri Suggestions", "US Holidays", "Birthdays", "Holidays"} then set end of myCalendars to item i of m
	end repeat
	set cals to choose from list myCalendars with title "Delete Old Events in Calendars" with prompt "Choose Calendars…" with multiple selections allowed
	if class of cals is boolean then return
	repeat with j from 1 to count cals
		set cal to item j of cals
		set progress description to "Old Calendar Events"
		set progress total steps to -1 --(count eventList)
		set progress completed steps to 0
		set progress additional description to "Getting Events for Calendar \"" & cal & "\""
		tell application "Calendar"
			with timeout of 600 seconds
				set rawEvents to {uid, summary, start date, recurrence} of events of calendar cal
			end timeout
		end tell
		set c to count item 1 of my rawEvents
		if c > 0 then
			repeat with i from 1 to c
				set anEvent to {item i of item 1 of my rawEvents, item i of item 2 of my rawEvents, item i of item 3 of my rawEvents, item i of item 4 of my rawEvents}
				set end of my calEvents to anEvent
			end repeat
			quickSortMulti(calEvents, {3})
			--=calEvents
			set progress description to "Calendar Events (" & cal & " - " & (c as text) & ")"
			set progress total steps to c
			set progress completed steps to 0
			set progress additional description to ""
			set myDate to item 3 of item 1 of calEvents
			set m to month of myDate
			tell application "Calendar"
				switch view to month view
				view calendar at myDate
			end tell
			repeat with i from 1 to c -- anEvent in rawEvents
				set anEvent to item i of calEvents --contents of anEvent
				if (month of (item 3 of anEvent)) ≠ m then
					set m to month of (item 3 of anEvent)
					tell application "System Events" to tell application process "Calendar"
						click button 3 of group 1 of group 1 of splitter group 1 of window 1
					end tell
				end if
				set progress completed steps to progress completed steps + 1
				if (item 3 of anEvent) < oldDate then
					if item 4 of anEvent is missing value then
						set progress additional description to "Delete (" & (i as text) & ") " & (item 1 of anEvent) & " - \"" & (item 2 of anEvent) & "\" " & (item 3 of anEvent)
						
						tell application "Calendar"
							try
								tell calendar cal to delete event id (item 1 of anEvent)
								--tell calendar cal to delete (1st event whose uid is (item 1 of anEvent))
							end try
						end tell
					else
						set progress additional description to "Recurring (" & (i as text) & ") " & (item 1 of anEvent) & " - \"" & (item 2 of anEvent) & "\" " & (item 3 of anEvent)
					end if
				else
					exit repeat
				end if
			end repeat
		end if
	end repeat
	tell me to activate
	display alert "All Done!" giving up after 10
end run

on quickSortMulti(alist, sortList) -- llo as integer, hhi as integer -- Non-Recursive FASTEST
	local px, lo, hi, i, j, L, H, sw, c, comp -- px means 'Pivot Index'
	script m
		property nList : alist
		property sList : {}
		property oList : {}
		property stack : {}
	end script
	--set M's nList to alist
	repeat with i in sortList
		if i < 0 then -- if negative then sort descending
			set end of m's sList to -(contents of i)
			set end of m's oList to false
		else -- sort ascending
			set end of m's sList to (contents of i)
			set end of m's oList to true
		end if
	end repeat
	set end of m's stack to {1, count of m's nList}
	set i to 1
	repeat until (count of m's stack) = 0
		set lo to item 1 of item 1 of m's stack
		set hi to item 2 of item 1 of m's stack
		set m's stack to rest of m's stack
		-- partitionHoare
		set px to item ((hi + lo) div 2) of m's nList
		set L to lo
		set H to hi
		repeat
			set comp to true
			repeat while comp
				repeat with j from 1 to count of m's sList -- do multiple comparisons
					set c to item j of m's sList
					set comp to false
					if item c of item L of m's nList < item c of px then
						if item j of m's oList then set comp to true -- ascending
						exit repeat
					else if item c of item L of m's nList > item c of px then
						if not (item j of m's oList) then set comp to true --descending
						exit repeat
					end if
				end repeat
				if comp then set L to L + 1
			end repeat
			set comp to true
			repeat while comp
				repeat with j from 1 to count of m's sList -- do multiple comparisons
					set c to item j of m's sList
					set comp to false
					if item c of item H of m's nList > item c of px then
						if item j of m's oList then set comp to true -- ascending
						exit repeat
					else if item c of item H of m's nList < item c of px then
						if not (item j of m's oList) then set comp to true --descending
						exit repeat
					end if
				end repeat
				if comp then set H to H - 1
			end repeat
			if L ≥ H then exit repeat
			set sw to item L of m's nList
			set item L of m's nList to item H of m's nList
			set item H of m's nList to sw
			set L to L + 1
			set H to H - 1
		end repeat
		set px to H -- end of partitionHoare
		if px + 1 < hi then set beginning of m's stack to {px + 1, hi}
		if lo < px then set beginning of m's stack to {lo, px}
	end repeat -- help
end quickSortMulti

Calendar will happily accept :

set d to date "Sunday, 10 May 2020 at 00:00:00"
tell application "Calendar"
	delete (every event of calendar "Work" whose start date < d)
end tell

Are you looking for suggestions and ideas on improvements?

Your script seems unwieldy given the short description of the functionality.

If Otto’s suggestion of ‘delete every event whose start date…’ is too short for you, there are a few optimizations you can apply.

First, what’s with all the UI stuff? why the need to show the calendar at all? let alone switch the view to the month of the event in question before you delete it?
Screen updates take time - you can delete an event in less time than it takes just to activate the Calendar window, let alone switch to some month in the past.

It’s not like you give the user an opportunity to delete the event or not, so it’s not like they need to see the calendar with the event, only to see the event disappear.

Try removing all the stuff that changes Calendar’s view of the world and see what improvement you get.

Second optimization is one of which events to check.

I didn’t look at your sort function, but OMM it was pretty quick, so that’s not likely to be a big saver, but you could save some time by just discarding events that are newer than your cutoff date. You could either do this before sorting, or as part of the sort.

For example, instead of:

			repeat with i from 1 to c
				set anEvent to {item i of item 1 of my rawEvents, item i of item 2 of my rawEvents, item i of item 3 of my rawEvents, item i of item 4 of my rawEvents}
				set end of my calEvents to anEvent
			end repeat

add a check…

			repeat with i from 1 to c
				set anEvent to {item i of item 1 of my rawEvents, item i of item 2 of my rawEvents, item i of item 3 of my rawEvents, item i of item 4 of my rawEvents}
				if item 3 of anEvent < oldDate then set end of my calEvents to anEvent
			end repeat

In this way you only build a list of events that are old and worth deleting, meaning you don’t need to sort them at all.

I was using the view to watch as each Event would disappear from the Calendar.
I was using it to verify the Events were actually disappearing, and to see why it was taking so long.
Also it was fun.

As for the sort function, I wrote that years ago. It’s a quick sort that can sort on multiple items in a list of lists. It can also sort ascending or descending on an item by item basis.

Hi Robert.

Your ‘oldDate’ property and its value are compiled into the script. You should ideally set the date — or at least its year — in the runtime code. Otherwise you’ll have to recompile the script if you want to use it again after this year.

EDIT: Sorry. I take this back! The situation seems to have changed now. If I compile this script and wait a few seconds …

property oldTime : time string of (current date)

on run
	repeat
		set newTime to time string of (current date)
		display dialog "oldTime: " & oldTime & linefeed & "newTime: " & newTime
	end repeat
end run

… the time strings are the same on the first repeat, indicating that the property must be set on the first run. Only on subsequent repeats are the strings different. If I save the compiled script and reopen it, the property’s reset to the reopening time. If I save the script as an application and run it by double-clicking its icon, the first time shown in the dialog is the time the script was saved!

Yet another peculiarity to have to explain to newbies…. :slightly_frowning_face: