[ANN] CalendarLib, improved Calendar scripting

CalendarLib, a scripting library for calendar event scripting without Calendar.app, is now available here: www.macosxautomation.com/applescript/apps/CalendarLib.html

The advantages of using CalendarLib rather than Calendar.app are:

  • You don’t need to have Calendar.app running.

  • It’s much faster. Hugely so, especially if you have a busy schedule.

  • When you ask for events that fall between two dates, unlike Calendar.app it includes recurring events.

  • If you want to change how it works, you can edit it to suit.

The disadvantages are:

  • You need to instal two libraries, either in a central Script Libraries folder or within the bundle of applets.

  • It doesn’t support all the features of Calendar.app.

Here’s a sample of how to use it:

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

-- fetch properties of events for next week
set d1 to current date
set d2 to d1 + 7 * days
set theStore to fetch store
set theCal to fetch calendar "Home" cal type cal cloud event store theStore -- change to suit
set theEvents to fetch events starting date d1 ending date d2 searching cals {theCal} event store theStore
repeat with anEvent in theEvents
	log (event info for event anEvent)
	log (event identifier for event anEvent)
	log (event attendees for event anEvent)
	log (event recurrence for event anEvent)
end repeat

See the Web page and docs for details.

Hi Shane,
thanks so much for this, I’ve been trying to create some Calendar operations activated by a mail rule and it was fine scripting the Calendar except I could not delete events. So this alternate way is holding some promise. I’ve tried the CalendarLib script and it works perfectly when used alone in a script and also in a bundle, but when I combine it with “using terms from” and “on perform mail action with messages”… I run into problems.

For the following script I have saved as a script bundle and installed the CalendarLib and BridgePlus libraries inside the bundle, in the Script:Contents:Resources:Script Libraries folder.

This script is cut down from a larger script to try to nut out the problem. I’ve declared all the variables inside the script instead of extracting them from the incoming email. When this script is activated by the mail rule, nothing happens. I see the little gear symbol in the top right of the screen momentarily, and if I put in a “display dialog” before or after the calLib section of the script, it does appear but the CalLib section of the script seems to not get activated and the event does not get created.

I’ve tested the calLib section outside of the “using terms from” and “on perform mail action with messages” section and it works perfectly, so it seems like the “tell calLib” does not work within the “using terms from” section.

Any help appreciated.
David


use calLib : script "CalendarLib"
use scripting additions

using terms from application "Mail"
	on perform mail action with messages theSelectedMessages for rule theRule
		
		set futureFlag to false
		set allDay to false
		set MyStartDateString to "September 24, 2015 at 12:46 pm"
		set EventDTSTART to date MyStartDateString
		set MyEndDateString to "September 24, 2015 at 1:00pm"
		set EventDTEND to date MyEndDateString
		set EventSummary to "DM Test"
		set EventLocation to "Homebush"
		set EventDescr to "Welcome to the test invitation" & return & "We really hope you can come."
		
		tell calLib
			set theStore to its fetchStore()
			set theCal to its fetchCalendarNamed:"Conference Calls" ofType:1 fromStore:theStore
			set EventDraft to its makeEventInStore:theStore forCalendar:theCal withSummary:EventSummary dateStart:EventDTSTART dateEnd:EventDTEND isAllDay:allDay location:EventLocation notes:EventDescr
			set newEvent to its saveEvent:EventDraft inStore:theStore futureEvents:futureFlag
		end tell
		
	end perform mail action with messages
end using terms from

You can probably get away with replacing the “use terms from…” statement with a “use application…” statement. You can also make the code a bit more readable by using CalendarLib’s terminology. So:

use calLib : script "CalendarLib"
use scripting additions
use application "Mail"

on perform mail action with messages theSelectedMessages for rule theRule
	
	set futureFlag to false
	set allDay to false
	set MyStartDateString to "September 24, 2015 at 12:46 pm"
	set EventDTSTART to date MyStartDateString
	set MyEndDateString to "September 24, 2015 at 1:00pm"
	set EventDTEND to date MyEndDateString
	set EventSummary to "DM Test"
	set EventLocation to "Homebush"
	set EventDescr to "Welcome to the test invitation" & return & "We really hope you can come."
	
	set theStore to fetch store
	set theCal to fetch calendar "Conference Calls" cal type cal cloud event store theStore
	set EventDraft to create event event store theStore destination calendar theCal event summary EventSummary starting date EventDTSTART ending date EventDTEND runs all day allDay event location EventLocation event description EventDescr
	set newEvent to store event event EventDraft event store theStore future events futureFlag
	
end perform mail action with messages

If that still has problems, you will need to move the CalendarLib stuff into its own handler.

Thanks for the script Shane. For some reason when I first tried this, it didn’t compile. Then I noticed the changes in syntax that you had put in your code and thought I needed to clean it up. Well I fell down a rabbit hole for a couple of days trying out different things until I eventually returned to your original script which works fine now.:rolleyes:

I first ran it as a script with the two libraries in the User/Library/Script Libraries/ folder, then I tried saving it as a bundle with the two libraries moved to the script bundle’s internal Script Libraries folder. I had to put the BridgePlus library inside the CalendarLib’s internal Script Libraries folder, and then put the CalendarLib inside my script’s internal Script Libraries folder for it to work as a script bundle.

FWIW, you could probably get away without the nesting by adding use script “BridgePlus” to the main script, before the use script “CalendarLib” statement.

:wink: Nice one. I have so many more questions relating to CalendarLib!

When you said I can make the code more readable by using CalendarLib’s terminology, that is useful but confused me because that is not in the ReadMe file supplied with CalendarLib nor could I find that terminology in the CalendarLib script itself. As an example, in the documentation to create an event it says to use the following:


set theEvent to its makeEventInStore:theStore forCalendar:theCal withSummary:"A test event" dateStart:d1 dateEnd:d2 isAllDay:false location:"Around here" notes:"some notes"

but using the CalendarLib’s terminology you have said to use:


set theEvent to create event event store theStore destination calendar theCal event summary "A test event" starting date d1 ending date d2 runs all day false event location "Around here" event description "some notes"

I think I have transcribed the variables correctly between these two versions, because the names of the parameters are quite different - “notes” and “event description” for example. So, where do I find this other terminology?

Secondly, I think your latest script using CalendarLib’s terminology worked for me because it does not use the named parameters with colons in the handler calls. When I was trying to get it to work before, I had to remove the named parameters and just use the notation I’m familiar with where I put the parameters unnamed inside brackets after the name of the handler. Do I just need to study up on named parameters?

As you can see, I may be opening a Pandora’s box of questions and some of them may be of a more general nature rather than being specifically about the CalendarLib library. Let me know if you would rather I PM you.

The easiest way: put it in ~/Library/Script Libraries/, then in Script Editor choose File → Open Dictionary… You’ll see it in the list.

As far as I can see, there was nothing wrong with your original code – the defined terminology is more a convenience. But you can’t just remove the colons. Something like this:

           set theCal to its fetchCalendarNamed:"Conference Calls" ofType:1 fromStore:theStore

is equivalent to this:

           set theCal to its fetchCalendarNamed_ofType_fromStore_("Conference Calls", 1, theStore)

As you will see when you compile it.

Edited to fix path.

I tried copying the CalendarLib.scptd to the ~/Library/Scripting Additions/ folder and I also put it in the users/Library/Scripting Libraries/ folder. But when I choose file/open/library in Script Editor and find it in the list, it doesn’t open anything. It will open it as a file though, but that looks like the working mechanism of the script, not a library. I tried it in Script Debugger but that only offers me to choose an application to open a library from or a preset list of libraries. What am I doing wrong?

Sorry,my bad – I meant Script Libraries (not Scripting).

It should show a normal dictionary, like a scriptable app. Try quitting Script Editor and trying again.

It’s a bit tricky in SD. You need to drag the file over it’s icon, then press the option key, and let go.

The last-resort is to control-click on it in the Finder, and drag the .sdef file over SE or SD.

FYI, CalendarLib has had a minor upgrade to version 1.1.0, adding a new command for retrieving multiple calendars.

Of perhaps more interest, it has a new sibling, CalendarLib EC. The differences are:

  • It requires El Capitan or later

  • It removes the requirement for BridgePlus

  • Several parameters are now optional

Both are available from:

www.macosxautomation.com/applescript/apps/Script_Libs.html

I am a beginner/intermediate AppleScripter and am enjoying using Calendar Lib EC. But now I’ve realized that I can’t remove events.

I have fetched events and used info from those events to create new events, using a test calendar “ZIBTEST” which is a cloud calendar.

I have not successfully removed events. When I try to fetch all of the test events I have created and then remove them, I get an error.

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

set d1 to (current date) - 7 * days
set d2 to d1 + 102 * days
set theStore to fetch store
set theCal2 to fetch calendar "ZIBTEST" cal type cal cloud event store theStore
set theEvents to fetch events starting date d1 ending date d2 searching cals {theCal2} event store theStore
repeat with anEvent in theEvents
	try
		remove event anEvent event store theStore
	end try
end repeat

replies to this script:

tell current application
current date
→ date “Saturday, February 6, 2016 at 4:56:36 PM”
end tell
tell application “Script Editor”
«event !CLs!reE» given «class !Cst»:«class ocid» id «data optr0000000080913936B87F0000»
→ error number -1708
«event !CLs!reE» given «class !Cst»:«class ocid» id «data optr0000000080913936B87F0000»
→ error number -1708

. . .

«event !CLs!reE» given «class !Cst»:«class ocid» id «data optr0000000080913936B87F0000»
→ error number -1708
«event !CLs!reE» given «class !Cst»:«class ocid» id «data optr0000000080913936B87F0000»
→ error number -1708
«event !CLs!reE» given «class !Cst»:«class ocid» id «data optr0000000080913936B87F0000»
→ error number -1708
«event !CLs!reE» given «class !Cst»:«class ocid» id «data optr0000000080913936B87F0000»
→ error number -1708
«event !CLs!reE» given «class !Cst»:«class ocid» id «data optr0000000080913936B87F0000»
→ error number -1708
end tell

There are a total of approximately 100 events and correspondingly 100 errors.

Any advice appreciated–

William

Model: MacBook Pro (Retina, 15-inch, Mid 2015)
AppleScript: AppleScript 2.5
Browser: Version 48.0.2564.103 (64-bit)
Operating System: Mac OS X (10.10)

First, can you confirm you are running 10.11? That’s what the lib requires, but your post says you’re running 10.10.

Second, are these test events repeating events?

I’ve got eaxctly the same query - some guidance on how to invoke the remove command would be helpful!

	set theStore to fetch store
	set theCal to fetch calendar destination cal type cal cloud event store theStore
	set theEvents to fetch events starting date thestart ending date thestart + 365 * days searching cals theCal event store theStore
	repeat with anEvent in theEvents
		--set theStore to fetch store
		---set theCal to fetch calendar destination cal type cal cloud event store theStore
		--set theInfo to (event info for event anEvent)
		remove event anEvent event store theStore
	end repeat

What if you add some required instructions at the beginning of your script ?

use AppleScript version "2.5" -- requires OS X 10.11 or higher
use scripting additions
use framework "Foundation"
use framework "EventKit"
use script "CalendarLib EC" -- put this at the top of your scripts



set theStore to fetch store
set theCal to fetch calendar destination cal type cal cloud event store theStore
set theEvents to fetch events starting date thestart ending date thestart + 365 * days searching cals theCal event store theStore
repeat with anEvent in theEvents
	--set theStore to fetch store
	---set theCal to fetch calendar destination cal type cal cloud event store theStore
	--set theInfo to (event info for event anEvent)
	remove event anEvent event store theStore
end repeat

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) jeudi 21 février 2019 18:26:04

Hi folks- I’ve a series of scripts that use CalendarLib and since upgrading to Ventura, they’re throwing errors whenever called upon to do anything other than delete events.

On further digging, it works fine for fetching events and deleting them but fails when events are attempted to be added; it seems like it’s failing on these lines:

set theResult to (theEvent's status())
if theResult is missing value then
	theDict's setObject:"none" forKey:"event_status"
else
	set theResult to theResult as integer
	theDict's setObject:(item (theResult + 1) of {"none", "confirmed", "tentative", "canceled"}) forKey:"event_status"
end if

Browser: Safari 605.1.15
Operating System: macOS 12

The code you’ve shown is called as part of the event info for command. It runs fine here under Ventura (macOS 13.0.1).

That’s really odd Shane- just tried it again under 13.0.1 and get tbis error:

*** -[__NSDictionaryM setObject:forKey:]: object cannot be nil (key: event_creation_date)

Any thoughts as to what’s going on?

I’m not sure what’s happening there. You’re not the first person to report problems, but I can’t reproduce them, and they seem to be a bit erratic. Sorry I ca’t offer better news.

Hey Shane

Thank you for the amazing library, but I seem to have hit the same error a little while after the OP, hopefully you can replicate

use script "CalendarLib EC" version "1.1.5"
use AppleScript version "2.4" -- Yosemite (10.10) or later

use scripting additions

set SourceCalendarName to "VV"
set DestinationCalendarName to "VV Mirror"
set meetingProxy to "VV Meeting"
set today to current date

set numberofdays to 7
set startDay to (today)
set time of startDay to 0
set endDay to (startDay + (numberofdays * days))

set numberOfEventsAdded to 0

set theStore to (fetch store)
-- The original script only compares events' start and end dates.
set destCal to (fetch calendar DestinationCalendarName cal type cal cloud event store theStore) -- change calendar type to suit
set existingEventList to (fetch events starting date startDay ending date endDay searching cals {destCal} event store theStore)
repeat with thisEvent in existingEventList
	set {event_start_date:startDate, event_end_date:endDate} to (event info for event thisEvent)
	set thisEvent's contents to {startDate, endDate}
end repeat
set existingEventDates to existingEventList -- Use a different variable now for clarity.

-- Get the source calendar events which fall within the same date range.
set sourceCal to (fetch calendar SourceCalendarName cal type cal cloud event store theStore) -- change to suit
set sourceEventList to (fetch events starting date startDay ending date endDay searching cals {sourceCal} event store theStore)
repeat with newEvent in sourceEventList
	-- Get the data of interest for each source event.\
	--set {event_external_ID:eventID, event_start_date:startDate, event_end_date:endDate, all_day:isAllday, event_is_recurring:isRecurring, event_creation_date:today} to (event info for event newEvent)
	
	set eventInfo to event info for event newEvent
	
	set eventID to event_external_ID from eventInfo
	(*
	set eventID to event_external_ID from event info for event newEvent
	set startDate to event_start_date from event info for event newEvent
	set endDate to event_end_date from event info for event newEvent
	*)
	-- If its start and end dates aren't in existingEventDates, create a new event using the Calendar appplication..
	if (existingEventDates does not contain {{startDate, endDate}}) then
		
		tell application "Calendar"
			if startDate is not missing value then
				set destEvent to (make new event at end of calendar DestinationCalendarName's events with properties {start date:startDate, end date:endDate, summary:meetingProxy, allday event:isAllday})
				
				if (isRecurring) then
					set destEvent's recurrence to recurrence of event id eventID of calendar SourceCalendarName
				end if
				
				set end of existingEventDates to {startDate, endDate} -- if required
			end if
			set numberOfEventsAdded to numberOfEventsAdded + 1
		end tell
		
	end if
	
end repeat

CalendarLib EC’s event info for handler extracts the values of several properties from its EKEvent parameter and adds them with labels to an NSMutableDictionary. This is eventually coerced to an AS record and returned as such.

Values in dictionaries aren’t allowed to be something called nil in Objective-C. The error reported by both theboyler and vmax apparently occurs when event info for event attempts to add an “event_creation_date” entry to the dictionary with the value nil, which it gets from the event’s creationDate() property.

creationDate() is one of several properties inherited from EKCalendarItem that are noted in the current documentation as having the value nil if they’ve not been set. So ideally, the next version of CalendarLib EC ought to catch these. :slightly_smiling_face: