Speeding up AppleScript searching for events in large calendar

Hi everybody,

I’ve been struggling with my first AppleScript. So far this site has helped me along quite well (thanks to all of you!), now I’ve hit a bump “thanks” to my large (?) calendar file (or thanks to the slowness of AppleScript with Calendar?).

I’d like to add an alert to all future events in my “Teaching” calendar, which contain “Exam” in their summary.

The following works if I’m running it over a small Test-Calendar but times out when running on my actual calendar for teaching.


tell application "Calendar"
	tell calendar "Teaching"
		-- Find exams
		set Exams to (events where its summary contains "Exam")
		repeat with AnExam in Exams
			if (start date of AnExam) > (current date) then -- only modify future exams
				tell AnExam
					-- Add a message alarm
					make new display alarm at end of display alarms with properties {trigger interval:-5760} -- 4 days before
				end tell
			end if
		end repeat
	end tell
	reload calendars
end tell

I’ve found some hints to speed up:

  1. Loop over the events in “vanilla” AppleScript: I don’t think that works here as I have to modify the events (not just output some info).
  2. Change it over to ObjC: way over my head, although I’d have some experience in shell scritpting (bash).

I’ve also tried to first only find future events and then loop over those to look for exams. But it also times out.

Any pointers?

Thanks in advance, TD

Hi. Welcome to MacScripter.

I’d guess that the timeout is due to Calendar struggling to apply the ‘where’ filter to a large number of events.

Rather than looping through the events themselves, it would be faster (less slow) to fetch the relevant data in one go and loop through those in vanilla AppleScript. Something like this:

set now to (current date)

-- Fetch the data needed to identify the relevant events. (Lists of date, text, and text respectively.)
tell application "Calendar"
	set {startDates, summaries, UIDs} to {start date, summary, uid} of events of calendar "Teaching"
end tell

-- Work through these and act when a future date has the same list index as a summary text containing "Exam".
repeat with i from 1 to (count startDates)
	if ((item i of startDates comes after now) and (item i of summaries contains "Exam")) then
		-- Get the text with the same index in the UID list and use it to identify the event in the calendar.
		set thisUID to item i of UIDs
		tell application "Calendar"
			tell event id thisUID of calendar "Teaching"
				-- Add a message alarm
				make new display alarm at end of display alarms with properties {trigger interval:-5760} -- 4 days before
			end tell
		end tell
	end if
end repeat

This way, Calendar doesn’t have to think about which events’ start dates match the date criterion and only one command is used to fetch the summaries instead of one for each event. The only events addressed individually are those to which you want to add the alarms.

Thanks so much, that works!

It still takes a long time to fetch all the data (and it seems to fetch it three separate times for “startDates”, “summaries” and “UIDs”) but it doesn’t time out and ends up doing what I wanted.

I added some user feedback via “log” commands so I know what happened. Here my finalized script (which is mostly Nigel’s work) in case somebody is interested:


set now to (current date)
set counter to 0

-- Fetch the data needed to identify the relevant events. (Lists of date, text, and text respectively.)
tell application "Calendar"
	set {startDates, summaries, UIDs} to {start date, summary, uid} of events of calendar "Teaching"
end tell

-- Work through these and act when a future date has the same list index as a summary text containing "Exam".
repeat with i from 1 to (count startDates)
	if ((item i of startDates comes after now) and (item i of summaries contains "Exam")) then
		-- Get the text with the same index in the UID list and use it to identify the event in the calendar.
		set thisUID to item i of UIDs
		tell application "Calendar"
			tell event id thisUID of calendar "Teaching"
				-- Add a message alarm
				make new display alarm at end of display alarms with properties {trigger interval:-5760} -- 4 days before
				-- Keep track
				set counter to (counter + 1)
			end tell
		end tell
		log counter & " Added alerts to " & (item i of summaries) & "   on   " & (item i of startDates)
	end if
end repeat
log "  Added alerts to a total of " & counter & " exams  "

Thanks again for your help!

This may get the information a bit quicker, using my Calendar Lib EC script library (https://www.macosxautomation.com/applescript/apps/Script_Libs.html):

use script "CalendarLib EC" version "1.1.3" -- put this at the top of your scripts
use scripting additions

set d1 to current date -- change to suit
set d2 to d1 + 1000 * days -- change to suit
set theStore to fetch store
set theCal to fetch calendar "Teaching" cal type cal cloud event store theStore -- change cal type to suit
set theEvents to fetch events starting date d1 ending date d2 searching cals {theCal} event store theStore
-- filter by matching summary using wildcards
set theEvents to filter events by pattern event list theEvents event summary "* Exam *" without using regex
repeat with anEvent in theEvents
	set theID to event identifier for event anEvent
	tell application "Calendar"
		tell event id thisUID of calendar "Teaching"
			-- Add a message alarm
			make new display alarm at end of display alarms with properties {trigger interval:-5760} -- 4 days before
		end tell
	end tell
end repeat

A year later (now on Catalina) I’ve tried to add my calendar alarms again. But now I’m getting long errors when I’m trying to add my alarms to an event.

I’ve tried using my script above as well as Shane’s Script using the CalendarLib EC library (version 1.1.4). In both cases I get an error as posted below.

One change is that this year I have repeating events where I change individual events into “Exams”. My script above would not detect them while Shanes version would, I think. But even when creating an individual exam-event, I get the error when trying to add an alarm.

Anybody can help me with what happened? Is support for Calendar scripting vanishing?

Thanks so much.

ThulDai

error “Calendar got an error: Failed to save event [K3y – M – Exam!], with error [{
NSDetailedErrors = (
"Error Domain=NSCocoaErrorDomain Code=1570 \"action is a required value.\" UserInfo={\n CalAlarmUID = \"F5AA4B01-0715-489C-877E-0F293F3A9214\";\n CalCalendarItemUID = \"C2D3FABC-7FB4-4BEB-8C52-8BD14A8AC01D\";\n CalCalendarUID = \"DA5BF0C1-DA14-4846-ABF8-4DB6FB6E38AF\";\n CalManagedObjectType = CalManagedAlarm;\n NSLocalizedDescription = \"action is a required value.\";\n NSValidationErrorKey = action;\n NSValidationErrorObject = \"<CalManagedAlarm: 0x6000026b82d0> (entity: Alarm; id: 0x3d3bc5c5570eee5 x-coredata://5B7AC837-3FAC-4652-A15C-76D1D270DE61/Alarm/p584301; data: )\";\n}",
"Error Domain=NSCocoaErrorDomain Code=1570 \"action is a required value.\" UserInfo={\n CalAlarmUID = \"D93B2387-FFF7-4CBB-B51B-804CA0421F74\";\n CalCalendarItemUID = \"C2D3FABC-7FB4-4BEB-8C52-8BD14A8AC01D\";\n CalCalendarUID = \"DA5BF0C1-DA14-4846-ABF8-4DB6FB6E38AF\";\n CalManagedObjectType = CalManagedAlarm;\n NSLocalizedDescription = \"action is a required value.\";\n NSValidationErrorKey = action;\n NSValidationErrorObject = \"<CalManagedAlarm: 0x6000026a79d0> (entity: Alarm; id: 0x3d3bc5c55b0eee5 x-coredata://5B7AC837-3FAC-4652-A15C-76D1D270DE61/Alarm/p584302; data: )\";\n}",
"Error Domain=NSCocoaErrorDomain Code=1570 \"action is a required value.\" UserInfo={\n CalAlarmUID = \"9090CA59-179B-4E12-908E-5E4AE9641AD3\";\n CalCalendarItemUID = \"F9E6F55E-47AE-4A95-8405-A7165D7321C4\";\n CalCalendarUID = \"DA5BF0C1-DA14-4846-ABF8-4DB6FB6E38AF\";\n CalManagedObjectType = CalManagedAlarm;\n NSLocalizedDescription = \"action is a required value.\";\n NSValidationErrorKey = action;\n NSValidationErrorObject = \"<CalManagedAlarm: 0x6000026a4500> (entity: Alarm; id: 0x3d3bc5c54b0eee5 x-coredata://5B7AC837-3FAC-4652-A15C-76D1D270DE61/Alarm/p584298; data: )\";\n}",
"Error Domain=NSCocoaErrorDomain Code=1570 \"action is a required value.\" UserInfo={\n CalAlarmUID = \"7268D8AE-3AE9-4E6B-9453-5CE8ED6F39D4\";\n CalCalendarItemUID = \"F9E6F55E-47AE-4A95-8405-A7165D7321C4\";\n CalCalendarUID = \"DA5BF0C1-DA14-4846-ABF8-4DB6FB6E38AF\";\n CalManagedObjectType = CalManagedAlarm;\n NSLocalizedDescription = \"action is a required value.\";\n NSValidationErrorKey = action;\n NSValidationErrorObject = \"<CalManagedAlarm: 0x6000026a7de0> (entity: Alarm; id: 0x3d3bc5c54f0eee5 x-coredata://5B7AC837-3FAC-4652-A15C-76D1D270DE61/Alarm/p584299; data: )\";\n}",
"Error Domain=NSCocoaErrorDomain Code=1570 \"action is a required value.\" UserInfo={\n CalAlarmUID = \"B8121FD8-4227-43A2-85FE-674CA71868BE\";\n CalCalendarItemUID = \"C2D3FABC-7FB4-4BEB-8C52-8BD14A8AC01D\";\n CalCalendarUID = \"DA5BF0C1-DA14-4846-ABF8-4DB6FB6E38AF\";\n CalManagedObjectType = CalManagedAlarm;\n NSLocalizedDescription = \"action is a required value.\";\n NSValidationErrorKey = action;\n NSValidationErrorObject = \"<CalManagedAlarm: 0x6000026b8280> (entity: Alarm; id: 0x3d3bc5c5530eee5 x-coredata://5B7AC837-3FAC-4652-A15C-76D1D270DE61/Alarm/p584300; data: )\";\n}"
);
}]” number -10025

I am encountering the same error when trying to add an alarm to a calendar event and so far I have been unable to find a way to fix this. Can anyone give me any hint please?

Simply ignore this error. The display alarm will be created:


try -- this ignores the "strange" error. I think, this is some bug.
	make new display alarm at end of display alarms with properties {trigger interval:-5760}
end try

After making the new display alarm, add following:


tell application "Calendars" to reload calendars -- it is the command of application itself