Address Book Dates to iCal

Address Book Custom Dates to iCal

Had created this script since couple of days back I missed a friends Wedding Anniversary…

Anyways, this is the first version of the script. Plan to change it to update only changed contacts.

But it works for me now…
Check it out…

— ====== AppleScript Start ====== —

— Variables —

set iCalCalendar to “Address Book Dates”
set DateList to {}
set CalendarFound to false

— Logic —

tell application “iCal”
if not (exists calendar iCalCalendar) then
display dialog “Please create new Calendar called
'” & iCalCalendar & "’ with Info: Ignore alerts and Events affect availability off.

Then run this script again."
else
set CalendarFound to true
end if
end tell
if CalendarFound then
tell application “Address Book”
activate
repeat with this_person in every person
if (custom dates of this_person is not missing value) then
repeat with this_date in custom dates of this_person

				#set NewRecord to {name:label of thisperson & "," & tag:label of this_date & "," & value:value of this_date}
				set EntryName to missing value
				if first name of this_person is not missing value and last name of this_person is not missing value then
					set EntryName to first name of this_person & " " & last name of this_person
				else
					set EntryName to name of this_person
				end if
				
				set end of DateList to {PersonId:id of this_person, PersonName:EntryName, DateType:label of this_date, DateValue:value of this_date}
			end repeat
		end if
	end repeat
end tell

tell application "iCal"
	activate
	reload calendars
	tell calendar iCalCalendar
		set its color to {3598, 24929, 47545}
		set description to "via Address Book Dates to iCal.scpt"
		
		if (count of every event) > 0 then
			set AllEvents to every event
			repeat with current_event in AllEvents
				delete current_event
			end repeat
		end if
		
		repeat with dl in DateList
			set PersonId to PersonId of dl
			set PersonName to PersonName of dl
			set DateType to DateType of dl
			set DateValue to DateValue of dl
			set DateStr to missing value --date string of (DateValue as date)
			
			if (year of (DateValue as date)) = 1604 or (year of (DateValue as date)) = 1900 then
				set DateStr to day of (DateValue as date) & " " & month of (DateValue as date)
			else
				set DateStr to day of (DateValue as date) & " " & month of (DateValue as date) & " " & year of (DateValue as date)
			end if
			
			set EventTitle to missing value
			
			if DateType = "anniversary" then
				set DateType to "Anniversary"
				set EventTitle to "â­• " & PersonName & "'s " & DateType
			else if DateType = "engagement" then
				set DateType to "Engagement"
				set EventTitle to "❍ " & PersonName & "'s " & DateType
			else
				set EventTitle to "★ " & PersonName & "'s " & DateType
			end if
			
			set Desc to PersonName & "'s " & DateType & " (" & DateStr & ")"
			
			make new event with properties {description:Desc, url:"addressbook://" & PersonId, allday event:true, start date:DateValue, end date:DateValue, summary:EventTitle, recurrence:"FREQ=YEARLY;INTERVAL=1"}
		end repeat
	end tell
end tell

end if

— ====== AppleScript End ====== —

Hi.

A belated welcome to MacScripter and thanks for posting your script. The forum for posting unsolicited working code is Code Exchange. I’ll ask the administrators to transfer this thread there when you’ve had a chance to read where it’s going!

I love the Unicode symbols in your iCal summaries and may adopt something like that myself. However, there are a few errors and instances of sub-optimal coding in your script which I hope you won’t mind me pointing out:

A person’s ‘custom dates’ is never ‘missing value’ (unless Lion’s Address Book incorrectly returns it so). It’s always a list containing either nothing (no custom dates) or records of the custom dates’ properties. So the condition in the above ‘if’ statement is always ‘true’ and the code after it is always executed. This is actually harmless because iterating through an empty list simply does nothing, but it does mean you’re extracting the custom dates from each person twice when you only need do it once.

Since EntryName is always set either to text derived from the first and last names or to the ‘name’, there’s no point in setting it to ‘missing value’ first. And, as above, it’s more efficient to get data from an application just once and repeatedly retrieve it from a variable than it is to repeatedly retrieve it from the application. Something like:

set {first name:firstName, last name:lastName} to this_person
if ((firstName is missing value) or (lastName is missing value)) then
	set EntryName to name of this_person
else
	set EntryName to firstName & " " & lastName
end if

By the way, comparing the first name and the last name with ‘missing value’ is correct here, because ‘missing value’ is what Address Book and many other applications return for a property which should exist but hasn’t been defined. A ‘custom date’, on the other hand, is an element, of which there may legitimtely be zero or more.

Or simply:

delete every event

There’s no ‘as date’ coercion in AppleScript. Also, concatenating stuff to an integer (the ‘day’ value of the date) results in a list, not text. The integer should be coerced to text first:

set y to year of DateValue
if ((y = 1604) or (y = 1900)) then
	set DateStr to ((day of DateValue) as text) & " " & (month of DateValue)
else
	set DateStr to ((day of DateValue) as text) & " " & (month of DateValue) & " " & y
end if

-- Or:
set DateStr to ((day of DateValue) as text) & " " & (month of DateValue)
set y to year of DateValue
if not ((y = 1604) or (y = 1900)) then set DateStr to DateStr & " " & y

Your original concatention works in context because the list produced is itself concatenated to text a few lines further on and AppleScript’s text item delimiters haven’t been changed from their default value of {“”}.

Presumably, not including the year for 1604 and 1900 is to spare the feelings of some of your older friends. :wink:

Hi NG,

Thanks for your eval and feed back… Am still referencing your comments while working on the new script. :slight_smile:

Below is the modified Script as you suggested. Funny that my script stopped working after 10.7.4 update, but your incorporating your comments makes it work.

BTW, have created the icon for Application that I auto run on computer boot up.

Address Book Dates to iCal.icns:
http://www.mediafire.com/?30xemzggc2d0zjg

Address Book Dates to iCal.png:
http://www.mediafire.com/i/?hk41fk94ruj2c12

--- ====== AppleScript Start ====== ---

--- Variables ---

set iCalCalendar to "Address Book Dates"
set DateList to {}
set CalendarFound to false

--- Logic ---

tell application "Calendar"
	if not (exists calendar iCalCalendar) then
		display dialog "Please create new Calendar called
'" & iCalCalendar & "' with Info: Ignore  alerts and Events affect availability off.
The calendar may be created on iCloud

 Then run this script again."
	else
		set CalendarFound to true
	end if
end tell
if CalendarFound then
	tell application "Contacts"
		activate
		repeat with this_person in every person
			repeat with this_date in custom dates of this_person
				
				set {first name:firstName, last name:lastName} to this_person
				if ((firstName is missing value) or (lastName is missing value)) then
					set EntryName to name of this_person
				else
					set EntryName to firstName & " " & lastName
				end if
				
				set end of DateList to {PersonId:id of this_person, PersonName:EntryName, DateType:label of this_date, DateValue:value of this_date}
			end repeat
		end repeat
	end tell
	
	tell application "Calendar"
		activate
		reload calendars
		tell calendar iCalCalendar
			
			set its color to {3598, 24929, 47545}
			set description to "via Address Book Dates to iCal.scpt"
			
			delete every event
			
			repeat with dl in DateList
				set PersonId to PersonId of dl
				set PersonName to PersonName of dl
				set DateType to DateType of dl
				set DateValue to DateValue of dl
				set DateStr to ((day of DateValue) as text) & " " & (month of DateValue)
				
				set y to year of DateValue
				if not ((y = 1604) or (y = 1900)) then set DateStr to DateStr & " " & y
				
				set EventTitle to missing value
				
				if DateType = "anniversary" then
					set DateType to "Anniversary"
					set EventTitle to "⭕ " & PersonName & "'s " & DateType
				else if DateType = "engagement" then
					set DateType to "Engagement"
					set EventTitle to "❍ " & PersonName & "'s " & DateType
				else
					set EventTitle to "★ " & PersonName & "'s " & DateType
				end if
				
				set Desc to PersonName & "'s " & DateType & " (" & DateStr & ")"
				
				make new event with properties {description:Desc, url:"addressbook://" & PersonId, allday event:true, start date:DateValue, end date:DateValue, summary:EventTitle, recurrence:"FREQ=YEARLY;INTERVAL=1"}
			end repeat
		end tell
	end tell
end if

--- ====== AppleScript End ====== ---

Edit by NG 2021-08-30: Unicode characters restored (hopefully correctly) from an old text file and [applescript] posting tags added.

I don’t have a lot of scripting experience. I have used the above script for years, but after I migrated to Catalina and then Big Sur it doesn’t work. It throws and error “Calendar got an error: Can’t get event id”.

Anyone have any suggestions on how to fix this?

Thanks, in advance.

Hi splatz.

The script runs without error on my Mojave system, but of course that’s not Catalina or Big Sur.

The only mention of an id in either of the “Calendar” tell blocks is in the variable name PersonID. Have you by any chance split this into two words?

Hi Nigel,

Thanks for writing back so quickly! I tried separating them, but that didn’t work. Maybe I did it wrong.

One thing I do see is that before it fails, it sets someone’s anniversary to: ★ Joe Smith’s $!!$

It’s not catching that it’s an anniversary. It’s putting what you find in the last else:


                if DateType = "anniversary" then
                   set DateType to "Anniversary"
                   set EventTitle to "⭕ " & PersonName & "'s " & DateType
               else if DateType = "engagement" then
                   set DateType to "Engagement"
                   set EventTitle to "❍ " & PersonName & "'s " & DateType
               else
                   set EventTitle to "★ " & PersonName & "'s " & DateType
               end if

Ah. No. I was wondering if PersonID had accidentally been split into two words (a mistake I often make when typing in code), which would cause an error. It was a first guess based on the error message and the fact that there’s nothing obvious in the code that would make the error occur.


That’s interesting! It looks as if your Contacts is returning custom date labels bracketed with “$!<" and ">!$”. I vaguely recall seeing something like this before, but can’t remember the context now. It doesn’t happen on my own machine. Perhaps it’s your later version of Contacts. Or maybe your Contacts database is on iCloud and it’s something to do with that. Either way, it should be easily fixable for the current purpose by inserting an additional line into the code:

set DateType to DateType of dl -- Existing line.
if (DateType begins with "_$!<") then set DateType to text 5 thru -5 of DateType -- Insert this.
set DateValue to DateValue of dl -- Existing line.

The inserted line replaces any text beginning with “_$!<” with the text from its fifth character to the fifth character from the end. If you wanted to be really thorough, you could do the following, although I don’t think the need would arise:

set DateType to DateType of dl -- Existing line.
if ((DateType begins with "_$!<") and (DateType ends with ">!$_")) then
	set DateType to text 5 thru -5 of DateType
	if (DateType is "<>") then set DateType to ""
end if
set DateValue to DateValue of dl -- Existing line.

I can’t see that this would fix the Calendar error though, which is still a mystery. :confused:
• Is “Calendar got an error: Can’t get event id” the full error message?
• If you run the script in Script Editor, is anything in the code highlighted when the error occurs?
• Does the script create some or all of the events before failing?

Hi again Nigel. You’re on to something that I hasn’t noticed. August is the calendar showing and it doesn’t have any of the custom event titles, only anniversary dates. Other events create.

I added your recommended code and that fixed anniversary dates. :slight_smile:

set DateType to DateType of dl -- Existing line.
if (DateType begins with "_$!<") then set DateType to text 5 thru -5 of DateType -- Insert this.
set DateValue to DateValue of dl -- Existing line.

Now regarding the error message and position when error occurs.

The full error message is:

I only run it in the Script Editor. Should I do it a different way?

I ran it again and noted that it is failing on the following line. I am sorry, I totally forgot that years ago I added alarms. That’s what’s failing, both of these fail:


delete every sound alarm
delete every display alarm

Since I forgot I’d changed the code, this is the alarms code:

--------------------------------------------------------------------- SET REMINDERS for new dates --
	tell application "Calendar"
		
		tell calendar "Address Book Dates"
			
			set all_events to every event
			
			repeat with this_event in all_events
				
				tell this_event
					
					delete every sound alarm
					--delete every sound
					
					delete every display alarm
					--make new display alarm at end with properties {trigger interval:-2160}
					--make new display alarm at end with properties {trigger interval:-10800}
					--make new display alarm at end with properties {trigger interval:480}
					
					
					-- -20880 minutes = 348 hours before = 14.5 days before
					make new sound alarm at end with properties {trigger interval:-20880, sound name:"basso"}
					
					-- -10800 minutes = 180 hours before = 7.5 days before
					make new sound alarm at end with properties {trigger interval:-10800, sound name:"basso"}
					
					-- -2160 minutes = 36 hours before = 1.5 days before
					make new sound alarm at end with properties {trigger interval:-2160, sound name:"basso"}
					
					-- 480 minutes = 8 hrs after
					make new sound alarm at end with properties {trigger interval:480, sound name:"basso"}
					
					-- 1080 minutes = 18 hrs after
					make new sound alarm at end with properties {trigger interval:1080, sound name:"basso"}
					
				end tell
				
			end repeat -- repeat with this_event in all_events
			
		end tell -- tell calendar "Address Book Dates"
		
	end tell -- tell application "iCal"

Hi splatz.

You don’t say whereabouts in the script you’ve inserted the alarms code! If it’s tagged on at the very end, it should work OK. On my Mojave system though, each AppleScript-created event is created with a default sound alarm which the delete command totally ignores. (Possibly a bug in Calendar 11.0.) The command does delete any other alarms, but since the script deletes all the calendar’s existing events (unless you’ve changed that too) and creates new ones each time, there are no other alarms to delete before the new ones are added, so there’s no point in including commands to delete them.

More efficient than searching out the events again in a separate repeat at the end is to add the sound alarms to each event while you have it to hand after creating it:

--- ====== AppleScript Start ====== ---

--- Variables ---

set iCalCalendar to "Address Book Dates"
set DateList to {}
set CalendarFound to false

--- Logic ---

tell application "Calendar"
	if not (exists calendar iCalCalendar) then
		display dialog "Please create new Calendar called
'" & iCalCalendar & "' with Info: Ignore  alerts and Events affect availability off.
The calendar may be created on iCloud

 Then run this script again."
	else
		set CalendarFound to true
	end if
end tell
if CalendarFound then
	tell application "Contacts"
		activate
		repeat with this_person in every person
			repeat with this_date in custom dates of this_person
				
				set {first name:firstName, last name:lastName} to this_person
				if ((firstName is missing value) or (lastName is missing value)) then
					set EntryName to name of this_person
				else
					set EntryName to firstName & " " & lastName
				end if
				
				set end of DateList to {PersonId:id of this_person, PersonName:EntryName, DateType:label of this_date, DateValue:value of this_date}
			end repeat
		end repeat
	end tell
	
	tell application "Calendar"
		activate
		reload calendars
		tell calendar iCalCalendar
			
			set its color to {3598, 24929, 47545}
			set description to "via Address Book Dates to iCal.scpt"
			
			delete every event -- Deletes the calendar's existing events, if any.
			
			repeat with dl in DateList
				set PersonId to PersonId of dl
				set PersonName to PersonName of dl
				set DateType to DateType of dl -- Existing line.
				if (DateType begins with "_$!<") then set DateType to text 5 thru -5 of DateType -- Insert this.
				set DateValue to DateValue of dl -- Existing line.
				set DateValue to DateValue of dl
				set DateStr to ((day of DateValue) as text) & " " & (month of DateValue)
				
				set y to year of DateValue
				if not ((y = 1604) or (y = 1900)) then set DateStr to DateStr & " " & y
				
				set EventTitle to missing value
				
				if DateType = "anniversary" then
					set DateType to "Anniversary"
					set EventTitle to "⭕ " & PersonName & "'s " & DateType
				else if DateType = "engagement" then
					set DateType to "Engagement"
					set EventTitle to "❍ " & PersonName & "'s " & DateType
				else
					set EventTitle to "★ " & PersonName & "'s " & DateType
				end if
				
				set Desc to PersonName & "'s " & DateType & " (" & DateStr & ")"
				
				set this_event to (make new event with properties {description:Desc, url:"addressbook://" & PersonId, allday event:true, start date:DateValue, end date:DateValue, summary:EventTitle, recurrence:"FREQ=YEARLY;INTERVAL=1"})
				-- SET REMINDERS for this event.
				tell this_event
					
					-- Try giving the event a little time to come into being.
					set t to 0
					repeat until ((it exists) or (t = 15)) -- 15 * 0.2 = 3 seconds (or a little over with the existence check).
						delay 0.2
						set t to t + 1
					end repeat
					
					delete every sound alarm -- Ignores a sound alarm added by default in Mojave.
					
					-- delete every display alarm
					--make new display alarm at end with properties {trigger interval:-2160}
					--make new display alarm at end with properties {trigger interval:-10800}
					--make new display alarm at end with properties {trigger interval:480}
					
					
					-- -20880 minutes = 348 hours before = 14.5 days before
					make new sound alarm at end with properties {trigger interval:-20880, sound name:"basso"}
					
					-- -10800 minutes = 180 hours before = 7.5 days before
					make new sound alarm at end with properties {trigger interval:-10800, sound name:"basso"}
					
					-- -2160 minutes = 36 hours before = 1.5 days before
					make new sound alarm at end with properties {trigger interval:-2160, sound name:"basso"}
					
					-- 480 minutes = 8 hrs after
					make new sound alarm at end with properties {trigger interval:480, sound name:"basso"}
					
					-- 1080 minutes = 18 hrs after
					make new sound alarm at end with properties {trigger interval:1080, sound name:"basso"}
					
				end tell
			end repeat
		end tell
	end tell
end if

--- ====== AppleScript End ====== ---

Hi Nigel,

Thanks for the script. My apologies for not mentioning where the alarms code is located. It is located after the calendar events are created. I didn’t think about having it in the repeat.???

I tried the script you provided and it errored out on the:


delete every alarm

I commented it out and then it failed on first:


make new sound alarm at end with properties {trigger interval:-20880, sound name:"basso"}

Here’s the error:

I appreciate your help!

Hi splatz.

It’s certainly a perplexing problem. :confused: If you’re getting that error with the script I posted, it’s indicating that the script’s creating the first event OK (only one event before the error with my version) and is getting an id back for it, but can’t then access it to delete or add alarms. :confused:

My best guess at the moment (which may be total rubbish) is that you keep your Calendar data somewhere on the Internet and that there’s some network delay between the script sending the command to create an event and the database actually having the event ready when the script tells it to delete or add alarms.

I’ve added a delay repeat immediately before the ‘delete every sound alarm’ line in the script in post #9. It repeats until either the event exists or three seconds has elapsed, whichever happens sooner. If your Calendars are on iCloud or somewhere similar, you might like to give it a try and tell me how it goes. Fingers crossed ….

I’ll be away this weekend, so I may not be able to come back to this until next week.

Thanks, Nigel, for the update.

When I ran your script, I received this error on

delete every sound alarm

error “Calendar got an error: AppleEvent handler failed.” number -10000

I commented out that line and ran it again and I received the following error on

make new sound alarm at end with properties {trigger interval:-20880, sound name:"basso"}

error “Calendar got an error: Can’t get event id "E260EC65-2059-43E6-BDB1-50FE85EAD45B" of calendar id "1EFEBB79-F500-46A0-BCF1-BDE63928C7BB".” number -1728 from event id “E260EC65-2059-43E6-BDB1-50FE85EAD45B” of calendar id “1EFEBB79-F500-46A0-BCF1-BDE63928C7BB”

Hi splatz. Thanks for the feedback.

As one last try, I’ve made another version (below) in the form I think you had before: ie. separate repeats for creating the events and adding the alarms. Between the repeats, I’ve added instructions for Calendar to quit, reopen, and explicitly to reload its calendars. The hope is that it’ll finally get the message update its events before trying to add the alarms!

Otherwise, I’m about out of ideas at the moment. :confused: Do let me know how it goes.

--- Variables ---

set iCalCalendar to "Address Book Dates"
set DateList to {}

--- Logic ---

tell application "Calendar"
	activate
	if not (exists calendar iCalCalendar) then
		display dialog "Please create a new Calendar called
'" & iCalCalendar & "' with Info: Ignore  alerts and Events affect availability off.
The calendar may be created on iCloud

 Then run this script again." buttons {"Exit"} default button 1 cancel button 1
		--return
	end if
end tell

tell application "Contacts"
	activate
	repeat with this_person in every person
		repeat with this_date in custom dates of this_person
			
			set {first name:firstName, last name:lastName} to this_person
			if ((firstName is missing value) or (lastName is missing value)) then
				set EntryName to name of this_person
			else
				set EntryName to firstName & " " & lastName
			end if
			
			set end of DateList to {PersonId:id of this_person, PersonName:EntryName, DateType:label of this_date, DateValue:value of this_date}
		end repeat
	end repeat
end tell

tell application "Calendar"
	activate
	reload calendars
	tell calendar iCalCalendar
		
		set its color to {3598, 24929, 47545}
		set description to "via Address Book Dates to iCal.scpt"
		
		delete every event
		
		repeat with dl in DateList
			set PersonId to PersonId of dl
			set PersonName to PersonName of dl
			set DateType to DateType of dl -- Existing line.
			if (DateType begins with "_$!<") then set DateType to text 5 thru -5 of DateType -- Insert this.
			set DateValue to DateValue of dl -- Existing line.
			set DateStr to ((day of DateValue) as text) & " " & (month of DateValue)
			
			set y to year of DateValue
			if not ((y = 1604) or (y = 1900)) then set DateStr to DateStr & " " & y
			
			set EventTitle to missing value
			
			if DateType = "anniversary" then
				set DateType to "Anniversary"
				set EventTitle to "⭕ " & PersonName & "'s " & DateType
			else if DateType = "engagement" then
				set DateType to "Engagement"
				set EventTitle to "❍ " & PersonName & "'s " & DateType
			else
				set EventTitle to "★ " & PersonName & "'s " & DateType
			end if
			
			set Desc to PersonName & "'s " & DateType & " (" & DateStr & ")"
			
			make new event with properties {description:Desc, url:"addressbook://" & PersonId, allday event:true, start date:DateValue, end date:DateValue, summary:EventTitle, recurrence:"FREQ=YEARLY;INTERVAL=1"}
		end repeat
	end tell
	
	-- Overkill attempt to get Calendar to recognise the events it's just created.
	-- The hope is that by quitting and reopening it AND explicitly telling it to
	-- reload its calendars, it might actually update the events in them.
	quit
	repeat while (it is running)
		delay 0.2
	end repeat
	activate
	reload calendars
	
	-- splatz's original alarm-setting repeat.
	tell calendar iCalCalendar
		
		set all_events to every event
		
		repeat with this_event in all_events
			
			tell this_event
				
				delete every sound alarm -- Ignores a sound alarm added by default in Mojave.
				
				--delete every display alarm
				--make new display alarm at end with properties {trigger interval:-2160}
				--make new display alarm at end with properties {trigger interval:-10800}
				--make new display alarm at end with properties {trigger interval:480}
				
				
				-- -20880 minutes = 348 hours before = 14.5 days before
				make new sound alarm at end with properties {trigger interval:-20880, sound name:"basso"}
				
				-- -10800 minutes = 180 hours before = 7.5 days before
				make new sound alarm at end with properties {trigger interval:-10800, sound name:"basso"}
				
				-- -2160 minutes = 36 hours before = 1.5 days before
				make new sound alarm at end with properties {trigger interval:-2160, sound name:"basso"}
				
				-- 480 minutes = 8 hrs after
				make new sound alarm at end with properties {trigger interval:480, sound name:"basso"}
				
				-- 1080 minutes = 18 hrs after
				make new sound alarm at end with properties {trigger interval:1080, sound name:"basso"}
				
			end tell
			
		end repeat -- repeat with this_event in all_events
		
	end tell -- tell calendar "Address Book Dates"
	
end tell -- tell application "Calendar"

Maybe, following does the same. Not tested:


save
reload calendars

Hi KniazidisR.

Calendar doesn’t have a ‘save’ command — not Calendar 11.0 or earlier anyway.

My thinking otherwise is that maybe ‘reload calendar’ by itself, or quitting and reopening by themselves, would do the trick. But I can’t determine this myself as the script works for me whatever I do to it. So I’ve used the sledgehammer of quitting, reopening, and reloading in the hope that at least one of these will solve the problem splatz is having.

Hi Nigel,

Sorry for the delay. I’ve been unavailable. I won’t be able to test the for another couple of days. I’ll be in touch.

Thanks again for your help!
splatz