Calendar auto-increment name of event

calendar

If I set an event to repeat every day, is it possible to auto-increment a number at the end of its name?

E.g. every day at 21:00 I'd like an alert which says "Day #" where # is the number of days since the event started.

Best Answer

An AppleScript is one approach to doing this. See MacScripter / Calendar: Incrementing Name property for recurring events which has a script making each year an event that says "John Doe's #th Anniversary". The script is also below, for your convenience.

main()

on main()
   set calendar_name to "Anniversaries"
   set number_of_anniversaries to 75

   -- Get the required info from Contacts in iCalendar or intermediate form.
   set seed_data to get_seed_data()
   if (seed_data is {}) then
       display dialog "None of your contacts have anniversaries!" buttons {"!"} default button 1 with icon stop
   else
       -- Compose an iCalendar spec for a calendar containing the required events and save it to an .ics file on the desktop.
       set iCalendar_text to compose_ics(seed_data, number_of_anniversaries)
       write_ics_file(iCalendar_text, (path to desktop as text) & (calendar_name & ".ics"))

       -- Import the file into Calendar as a new calendar.
       import_calendar(calendar_name)
   end if
end main

-- Identify "anniversary" custom dates in Contacts and return the corresponding names, ids, and dates adapted for use in the construction of an iCalendar spec. Monogamous and once-only marriages assumed!
on get_seed_data()
   tell application "System Events" to set Contacts_open to (application process "Contacts") exists

   -- Get the name, id, and custom-date data for every person in Contacts. 
   tell application "Contacts"
       launch
       set {names, ids, {date_labels, date_values}} to {name, id, {label, value} of custom dates} of people
       if (not Contacts_open) then quit
   end tell

   -- Find any "anniversary" date labels and store the associated person/date data converted as follows:
   -- name: as the start of an iCalendar SUMMARY entry, with the name in the genitive and a trailing space.
   -- id: as a complete iCalendar URL entry, the id being part of an "addressbook"-protocol URL.
   -- "anniversary" date value: as event start and end dates in zoneless ISO format, but stored as integers instead of text for ease and speed of manipulation.
   set seed_data to {}
   repeat with person from 1 to (count names)
       set these_labels to item person of date_labels
       if (these_labels contains "anniversary") then
           repeat with custom_date from 1 to (count these_labels)
               if (item custom_date of these_labels is "anniversary") then
                   set start_date to item custom_date of item person of date_values
                   set end of seed_data to {|SUMMARY start|:"SUMMARY:" & (item person of names) & "'s ", |URL entry|:"URL;VALUE=URI:addressbook://" & item person of ids, |start date|:ISOT_integer(start_date), |end date|:ISOT_integer(start_date + days)}
                   exit repeat
               end if
           end repeat
       end if
   end repeat

   return seed_data
end get_seed_data

-- Put together an iCalendar specification for a calendar containing recurring events whose initial summaries indicate weddings and recurrence summaries indicate the inividual anniversaries.
on compose_ics(seed_data, number_of_anniversaries)
   -- The list of iCalendar components could get very long, so it'll be referenced via a script object for speed of access.
   script iCalendar
       property components : {}
   end script

   -- Initialise a VEVENT template. It'll serve for both main events and recurrence instances.
   set VEVENT_template to {"BEGIN:VEVENT", missing value, missing value, missing value, missing value, missing value, missing value, missing value, missing value, missing value, "END:VEVENT"}
   -- Get process and host data for use in the UIDs.
   set my_Name to name of current application
   tell application "System Events" to set unix_id to unix id of first application process whose displayed name is my_Name
   set host_name to host name of (system info)
   -- Prepare to use CRLF line endings.
   set CRLF to return & linefeed
   set astid to AppleScript's text item delimiters
   set AppleScript's text item delimiters to CRLF

   -- Create and store the opening VCALENDAR spiel.
   set beginning of iCalendar's components to {"BEGIN:VCALENDAR", "VERSION:2.0", "PRODID:-//Apple Inc.//iCal 4.0.4//EN", "CALSCALE:GREGORIAN"} as text

   -- Create and store the VEVENT sections for each main event.
   repeat with event_number from 1 to (count seed_data)
       set {|SUMMARY start|:SUMMARY_start, |URL entry|:URL_entry, |start date|:start_date, |end date|:end_date} to item event_number of seed_data
       -- Load the VEVENT template with the data for the main, recurring event.
       set item 2 of VEVENT_template to "CREATED:" & ISOT_GMT(current date)
       set item 3 of VEVENT_template to "UID:" & new_UID(unix_id, host_name, event_number)
       set item 4 of VEVENT_template to "DTEND;VALUE=" & end_date
       set leap_wedding to (start_date mod 10000 is 229) -- Did these idiots get married on 29th February?
       if (leap_wedding) then
           set using_Mar1 to (button returned of (display dialog (text 9 thru -1 of SUMMARY_start & "wedding is/was on 29th February! Are the non-leap anniversaries celebrated on 28th February or 1st March?") buttons {"28th February", "1st March"} default button 2 with icon note) is "1st March")
           if (using_Mar1) then -- The anniversaries have to recur on the 60th day of every year …
               set item 5 of VEVENT_template to "RRULE:FREQ=YEARLY;INTERVAL=1;BYYEARDAY=60;COUNT=" & (number_of_anniversaries + 1)
           else -- … or else on the last day of every February.
               set item 5 of VEVENT_template to "RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=2;BYMONTHDAY=-1;COUNT=" & (number_of_anniversaries + 1)
           end if
       else -- Normal anniversaries simply recur on the same date every year.
           set item 5 of VEVENT_template to "RRULE:FREQ=YEARLY;INTERVAL=1;COUNT=" & (number_of_anniversaries + 1)
       end if
       set item 6 of VEVENT_template to SUMMARY_start & "wedding"
       set item 7 of VEVENT_template to "DTSTART;VALUE=DATE:" & start_date
       set item 8 of VEVENT_template to "DTSTAMP:" & ISOT_GMT(current date)
       set item 9 of VEVENT_template to "SEQUENCE:7" -- Cheating with the sequence number!
       set item 10 of VEVENT_template to URL_entry

       -- Coerce the template to text and append the resulting VEVENT entry to the component list.
       set end of iCalendar's components to VEVENT_template as text

       -- Now create the required number of linked VEVENTS for the anniversaries, inserting the relevant dates and summaries into the template and coercing it to text each time.
       repeat with anniversary_number from 1 to number_of_anniversaries
           set start_date to start_date + 10000 -- Add 1 to the year.
           set end_date to end_date + 10000 -- Ditto.
           if (leap_wedding) then
               -- Use months and days pertinent to the anniverary-date convention.
               set y to start_date div 10000
               if (isLeapYear(y)) then
                   set start_date to y * 10000 + 229
                   set end_date to y * 10000 + 301
               else if (using_Mar1) then
                   set start_date to y * 10000 + 301
                   set end_date to y * 10000 + 302
               else
                   set start_date to y * 10000 + 228
               end if
           end if
           set item 4 of VEVENT_template to "DTEND;VALUE=DATE:" & end_date
           -- This entry links this VEVENT to an expression date of the original event's recurrence.
           set item 5 of VEVENT_template to "RECURRENCE-ID;VALUE=DATE:" & start_date
           set item 6 of VEVENT_template to SUMMARY_start & anniversary_number & ordinal(anniversary_number) & " anniversary"
           set item 7 of VEVENT_template to "DTSTART;VALUE=DATE:" & start_date
           set item 8 of VEVENT_template to "DTSTAMP:" & ISOT_GMT(current date)
           set item 9 of VEVENT_template to "SEQUENCE:8" -- Cheating with the sequence number!

           -- Append this linked-VEVENT text to the output components.
           set end of iCalendar's components to VEVENT_template as text
       end repeat
   end repeat

   -- Lastly, append the END:VCALENDAR line.
   set end of iCalendar's components to "END:VCALENDAR" & CRLF
   -- When all the components are gathered, coerce the lot into one text.
   set iCalendar_text to iCalendar's components as text

   set AppleScript's text item delimiters to astid

   return iCalendar_text
end compose_ics

on write_ics_file(iCalendar_text, hfs_path)
   set fRef to (open for access file hfs_path with write permission)
   try
       set eof fRef to 0
       write iCalendar_text as «class utf8» to fRef
   end try
   close access fRef
end write_ics_file

-- Delete any current calendar with the given name and import an .ics file to a replacement.
on import_calendar(calendar_name)
   tell application "Calendar"
       activate
       if (calendar calendar_name exists) then delete calendar calendar_name
       -- This assumes there'll be at most one calendar with the name, but it's easy to modify.
   end tell

   -- "Double-click" the ics file. Calendar will offer to import the new events.
   tell application "Finder" to open file (calendar_name & ".ics") of desktop

   -- In the Add Events dialog, select the bottom item ("New Calendar") in the pop-up menu and hit return. The calendar should be created automatically with the name of the ics file.
   tell application "System Events"
       tell application process "Calendar" -- GUI Scripting.
           repeat until (window 2 exists)
               delay 0.2
           end repeat
           tell pop up button 1 of window 1
               perform action "AXPress" -- Click the pop-up menu button.
               perform action "AXPress" of menu item -1 of menu 1 -- Click the "New Calendar" item.
           end tell
       end tell
       keystroke return -- "Click" the dialog's "OK" button.
   end tell
end import_calendar

-- Return the "yyyymmdd" representation of a date as an integer.
on ISOT_integer(theDate)
   set {year:y, month:m, day:d} to theDate
   return y * 10000 + m * 100 + d
end ISOT_integer

-- Return the GMT equivalent of a date in ISOT format.
on ISOT_GMT(theDate)
   set {year:y, month:m, day:d, time:t} to theDate - (time to GMT)
   return ((y * 10000 + m * 100 + d) as text) & "T" & text 2 thru -1 of ((1000000 + t div hours * 10000 + t mod hours div minutes * 100 + t mod minutes) as text) & "Z"
end ISOT_GMT

-- Construct an "@"-style UID from the current date and time and the given data.
on new_UID(unix_id, host_name, iteration)
   return ISOT_GMT(current date) & "-" & unix_id & "-" & iteration & "@" & host_name
end new_UID

on isLeapYear(y)
   return ((y mod 4 is 0) and (y mod 400 is not in {100, 200, 300}))
end isLeapYear

on ordinal(n)
   set units to n mod 10
   if ((units > 3) or ((n - units) mod 100 is 10) or (units < 1)) then
       return "th"
   else
       return item units of {"st", "nd", "rd"}
   end if
end ordinal