Un AppleScript es un enfoque para hacer esto.
Consulte MacScripter / Calendar: propiedad de Incremento del nombre para eventos recurrentes que tiene un script que hace de cada año un evento que dice "#Th aniversario de John Doe". El script también se encuentra a continuación, para su comodidad.
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