A small apology in advance. This is my first applescript and my first post here!
I am trying to write a script for events in iCal, to create a ToDo with the name of the event when the open file alarm from a recurring event triggers.
Apple doesn’t seem to contemplate recurring ToDo’s
However, it seems that the open file alarm, although running the file, does not pass anything to it - in other words I can’t work out which event has triggered it. (if anyone can tell me how to work this out, i need go no further)
I have resorted to trying to do it manually, ie to work though a list of all of the events in all calendars, recurring or otherwise, for which the event has not yet occurred but for which the time on the open file alarm has already past. So what I need to do is set a object to contain all events of myCal whose open file alarm’s filepath ends with “iCalToToDo.app”
but this doesn’t work (I didn’t really expect it to)
tell application "iCal"
repeat with myCal in calendars
set myEvents to (events of myCal whose open file alarm's filepath ends with "iCalToToDo.app")
end repeat
end tell
Is there a simple way of doing this?
As an alternative, I have tried nesting a long sequence of set/repeat commands to work through each layer of properties to get to the relevant events that have an alarm with this filepath one by one. It is below, but doesn’t yet work. It’s late and I am going to bed!
Finally, I’d like to know how to add the calendar events, as I identify each one, to an object so that I can then pass the whole lot to a handler to deal with them all at once. Whether or not I need to do that (I appreciate I could just pass them to the handler one by one), I’d really like to know how to do it. ie although I can “set myEvents to (events of myCal)”, which seems to create an object containing calendar events that I can then pass to a handler, I don’t know how to start that from scratch and add events to myEvents one by one.
Any help appreciated. My apologies if these questions are obvious (or even stupid), but I am stuck. I have read the tutorials and as much other guff on the web as I can handle, but have not found my answer.
Regards
AndyR
PS I have found and used the excellent script by Nigel Garvey at http://macscripter.net/viewtopic.php?pid=94958 to provide the next recurrence of an event.
PPS In case it helps, my full script to attempt to just identify the relevant events in the next two weeks follows:
set myStart to date (date string of ((current date)))
set myEnd to myStart + 14 * days
tell application "iCal"
set listOfEvents to {} -- sets a blank checklist
repeat with myCal in calendars -- first layer - deals with multiple calendars
set myEvents to (events of myCal)
repeat with myEvent in myEvents -- second layer - gets each event
if ((count recurrence of myEvent) > 0) then -- This is a recurring event.
set myOccurence to my getNextRecurrence(recurrence of myEvent, start date of myEvent, (current date)) -- uses this handler to return the next event date of the recurring event
if myOccurence is greater than or equal to myStart and myOccurence is less than or equal to myEnd then -- checks that it is within my two week period
set myOpenPathAlarms to open file alarms in myEvent --- third layer, those with open file alarms
repeat with myOpenPathAlarm in myOpenPathAlarms
set myFilePath to filepath of myOpenPathAlarm
if myFilePath ends with "iCalToToDo.app" then
try
make todo at end of todos of myCal with properties {summary:(summary of myEvent), due date:(start date of myEvent)} -- makes a todo using that info
copy summary of myEvent to end of listOfEvents -- adds it to my checklist so that I can see what is going on
end try
end if
end repeat
end if
end if
end repeat
end repeat
return listOfEvents -- returns my checklist
end tell
(* With many thanks to Nigel Garvey at http://macscripter.net/viewtopic.php?pid=94958 for getNextOccurrence , which follows *)
(* The vanilla code in this script is AS 1.9.1 compatible. *)
(* Oversee the calculation of the next due recurrence date (if any) of an iCal
event, given its recurrence value, start date, and the date of the check. Return
the recurrence date or, if the event's expired, missing value. *)
on getNextRecurrence(RFC2445, startDate, today)
set startDate's time to 0
set today's time to 0
set nextRecurrence to missing value -- In case we don't find anything.
if (startDate comes before today) then -- The start date's in the past.
if ((count RFC2445) > 0) then -- This is a recurring event.
considering case
set endDate to getEndDate(RFC2445)
if (endDate does not come before today) then -- Not yet expired.
set interval to getInterval(RFC2445)
set maxRecurrences to getMaxRecurrences(RFC2445)
set freq to getRule(RFC2445, "FREQ")
if (freq is "DAILY") then
set nextRecurrence to nextDaily(startDate, today, endDate, interval, maxRecurrences)
else if (freq is "WEEKLY") then
set nextRecurrence to nextWeekly(RFC2445, startDate, today, endDate, interval, maxRecurrences)
else if (freq is "MONTHLY") then
set nextRecurrence to nextMonthly(RFC2445, startDate, today, endDate, interval, maxRecurrences)
else if (freq is "YEARLY") then
set nextRecurrence to nextYearly(RFC2445, startDate, today, endDate, interval, maxRecurrences)
end if
end if
end considering
end if
else -- A start date in the present or future is any event's (recurring or not) "next recurrence".
set nextRecurrence to startDate
end if
return nextRecurrence
end getNextRecurrence
(* Get the next due recurrence date of a "DAILY" event. (Usually today.) *)
on nextDaily(startDate, today, endDate, interval, maxRecurrences)
set interval to interval * days
set nextRecurrence to today - (today - days - startDate) mod interval + interval - days
if ((nextRecurrence - startDate) div interval + 1 > maxRecurrences) or (nextRecurrence comes after endDate) then set nextRecurrence to missing value
return nextRecurrence
end nextDaily
(* Get the next due recurrence date of a "WEEKLY" event. *)
on nextWeekly(RFC2445, startDate, today, endDate, interval, maxRecurrences)
set BYDAYspecified to (RFC2445 contains "BYDAY")
if (BYDAYspecified) then
-- Get the offsets (in seconds) of the specified week-start weekday from Sunday and the recurrence weekdays from that.
set {WKSToffset, weekdayOffsets} to getWeekdayInstanceStuff(RFC2445)
-- Work out the start date of the week that starts on the WKST weekday and contains the event's start date.
set weekStart to startDate - (startDate - ((«data isot313030302D30312D3035» as date) + WKSToffset)) mod weeks
else
-- Otherwise, regard the event's start date as the week start and use an offset of 0.
set weekStart to startDate
set weekdayOffsets to {0}
end if
set recurrencesPerWeek to (count weekdayOffsets)
set interval to interval * weeks
-- Run the recurrence sequence from the week of the start date, starting with the first weekday in the list.
-- Start counting after passing the start date, which has already been checked in getNextRecurrence().
-- Continue to the first recurrence on or after today, or until maxRecurrences is exceeded.
set nextRecurrence to startDate
set recurrenceNo to 1 -- Recurrence 1 is the start date.
set i to 0 -- Index into weekdayOffsets.
repeat while (nextRecurrence comes before today) and (recurrenceNo < maxRecurrences)
set i to i + 1
if (i > recurrencesPerWeek) then
set i to 1
set weekStart to weekStart + interval
end if
set nextRecurrence to weekStart + (item i of weekdayOffsets)
if (nextRecurrence comes after startDate) then set recurrenceNo to recurrenceNo + 1
end repeat
-- No next recurrence if maxRecurrences is exceeded or endDate passed.
if (nextRecurrence comes before today) or (nextRecurrence comes after endDate) then set nextRecurrence to missing value
return nextRecurrence
end nextWeekly
(* Get the next due recurrence date of a "MONTHLY" event. *)
on nextMonthly(RFC2445, startDate, today, endDate, interval, maxRecurrences)
set BYDAYspecified to (RFC2445 contains "BYDAY=")
if (BYDAYspecified) then
set {weekdayCode, weekdayInstanceNos} to getWeekdayInstanceStuff(RFC2445)
set recurrencesPerMonth to (count weekdayInstanceNos)
else
set dayNumbers to getTargetDays(RFC2445, startDate)
set recurrencesPerMonth to (count dayNumbers)
end if
-- Run the recurrence sequence from the month of the start date, starting with the first recurrence day or weekday in the list.
-- Start counting after passing the start date, which has already been checked in getNextRecurrence().
-- Continue to the first valid occurrence on or after today, or until maxRecurrences is exceeded.
copy startDate to nextRecurrence
set recurrenceNo to 1 -- Recurrence 1 is the start date.
set dayInMonth to true -- Changed to false when a recurrence day doesn't exist in the month.
set i to 0 -- Index into weekdayInstanceNos or dayNumbers.
repeat while ((nextRecurrence comes before today) or not (dayInMonth)) and (recurrenceNo < maxRecurrences)
set i to i + 1
if (i > recurrencesPerMonth) then
set i to 1
set nextRecurrence to addMonths(nextRecurrence, interval)
end if
if (BYDAYspecified) then
-- Get the date of the specified weekday in this month.
set nextRecurrence to getWeekdayDate(nextRecurrence, weekdayCode, item i of weekdayInstanceNos)
set dayInMonth to true
else
-- Get the date of the (next) specified numbered day in this month.
set nextRecurrence's day to item i of dayNumbers
-- If the day doesn't exist in the month, the day and month will now have changed.
set dayInMonth to (nextRecurrence's day is result)
if not (dayInMonth) then set nextRecurrence to nextRecurrence - weeks -- Correct the month.
end if
if (dayInMonth) and (nextRecurrence comes after startDate) then set recurrenceNo to recurrenceNo + 1
end repeat
-- No next recurrence if maxRecurrences is exceeded or endDate passed.
if (nextRecurrence comes before today) or (nextRecurrence comes after endDate) then set nextRecurrence to missing value
return nextRecurrence
end nextMonthly
(* Get the next due recurrence date of a "YEARLY" event. *)
on nextYearly(RFC2445, startDate, today, endDate, interval, maxRecurrences)
set targetMonths to getTargetMonths(RFC2445, startDate)
set recurrencesPerYear to (count targetMonths)
set BYDAYspecified to (RFC2445 contains "BYDAY=")
if (BYDAYspecified) then set {weekdayCode, weekdayInstanceNos} to getWeekdayInstanceStuff(RFC2445)
-- Run the recurrence sequence from the year of the start date, starting with the relevant day of the first month in the list.
-- Start counting after passing the start date, which has already been checked in getNextRecurrence().
-- Continue to the first occurrence on or after today, or until maxRecurrences is exceeded.
copy startDate to nextRecurrence
set recurrenceNo to 1 -- Recurrence 1 is the start date.
set defaultDayNo to startDate's day
set dayInMonth to true -- Set to false when a recurrence day doesn't exist in a target month.
set i to 0 -- Index into targetMonths.
repeat while ((nextRecurrence comes before today) or not (dayInMonth)) and (recurrenceNo < maxRecurrences)
set i to i + 1
if (i > recurrencesPerYear) then
set i to 1
set nextRecurrence's year to (nextRecurrence's year) + interval
end if
set targetMonth to item i of targetMonths -- Get the next specified month.
if (BYDAYspecified) then
-- Get the date of the specified weekday in this month of this year.
set nextRecurrence's day to 1
set nextRecurrence's month to targetMonth
set nextRecurrence to getWeekdayDate(nextRecurrence, weekdayCode, beginning of weekdayInstanceNos)
else
-- Get the date of the specified numbered day in this month of this year.
set nextRecurrence's day to defaultDayNo
set nextRecurrence's month to targetMonth
end if
-- If the day doesn't exist in the month, the day and month will now have changed.
set dayInMonth to (nextRecurrence's month is targetMonth)
if (dayInMonth) and (nextRecurrence comes after startDate) then set recurrenceNo to recurrenceNo + 1
end repeat
-- No next recurrence if maxRecurrences is exceeded or endDate passed.
if (nextRecurrence comes before today) or (nextRecurrence comes after endDate) then set nextRecurrence to missing value
return nextRecurrence
end nextYearly
(* Odd jobs. *)
(* Read a given rule from the event's 'recurrence' text. *)
on getRule(RFC2445, ruleKey)
set astid to AppleScript's text item delimiters
set AppleScript's text item delimiters to ruleKey & "="
set rule to text item 2 of RFC2445
set AppleScript's text item delimiters to ";"
set rule to text item 1 of rule
set AppleScript's text item delimiters to astid
return rule
end getRule
(* Get the recurrence's "UNTIL" date (if specified) in AppleScript form or default to 31st December 9999. (Ignore time.) *)
on getEndDate(RFC2445)
set endDate to «data isot393939392D31322D3331» as date -- 31st December 9999.
if (RFC2445 contains "UNTIL") then
set n to (text 1 thru 8 of getRule(RFC2445, "UNTIL")) as integer
set endDate's day to n mod 100
set endDate's year to n div 10000
set endDate's month to item (n mod 10000 div 100) of {January, February, March, April, May, June, July, August, September, October, November, December}
end if
return endDate -- +- (time to GMT)?
end getEndDate
(* Get the event's recurrence interval (if specified) or default to 1. *)
on getInterval(RFC2445)
if (RFC2445 contains "INTERVAL") then return getRule(RFC2445, "INTERVAL") as integer
return 1
end getInterval
(* Get the event's recurrence count (if specified) or default to an arbitrarily high number. *)
on getMaxRecurrences(RFC2445)
if (RFC2445 contains "COUNT") then return getRule(RFC2445, "COUNT") as integer
return 1000000
end getMaxRecurrences
(* Return a list of the text items in a comma-delimited rule result. *)
on getListFromRule(rule)
set astid to AppleScript's text item delimiters
set AppleScript's text item delimiters to ","
set theList to rule's text items
set AppleScript's text item delimiters to astid
return theList
end getListFromRule
(* Derive a list of AppleScript months from the numbers in a "BYMONTH" rule or default to the month of the event's start date. *)
on getTargetMonths(RFC2445, startDate)
if (RFC2445 contains "BYMONTH=") then
set targetMonths to getListFromRule(getRule(RFC2445, "BYMONTH"))
set monthList to {January, February, March, April, May, June, July, August, September, October, November, December}
repeat with thisMonth in targetMonths
set thisMonth's contents to item (thisMonth as integer) of monthList
end repeat
else
set targetMonths to {startDate's month}
end if
return targetMonths
end getTargetMonths
(* Get a list of the day numbers specified in a "BYMONTHDAY" rule or default to the day of the event's start date. *)
on getTargetDays(RFC2445, startDate)
if (RFC2445 contains "BYMONTHDAY=") then
set targetDays to getListFromRule(getRule(RFC2445, "BYMONTHDAY"))
repeat with thisDay in targetDays
set thisDay's contents to thisDay as integer
end repeat
else
set targetDays to {startDate's day}
end if
return targetDays
end getTargetDays
(* Get and analyse the weekday(s) specified in a "BYDAY" rule, returning appropriate results. *)
on getWeekdayInstanceStuff(RFC2445)
set BYDAY to getRule(RFC2445, "BYDAY")
if (RFC2445 contains "WEEKLY") then
-- In a "WEEKLY" environment, return a list of offsets, in seconds, of the specified weekdays from the specified week start.
-- The week start itself is returned as an offset in seconds from Sunday.
set weekdayCodes to "SUMOTUWETHFRSA"
set WKSToffset to (offset of getRule(RFC2445, "WKST") in weekdayCodes) div 2 * days
set weekdayOffsets to getListFromRule(BYDAY)
repeat with thisEntry in weekdayOffsets
-- Each weekday offset must be the offset AFTER or including the week start.
set thisEntry's contents to ((offset of thisEntry in weekdayCodes) div 2 * days + weeks - WKSToffset) mod weeks
end repeat
return {WKSToffset, weekdayOffsets}
else
-- In a "MONTHLY" environment, only one weekday is specified, along with an instance-in-month figure.
-- The standard allows for the lack of an instance number to mean "every instance in the month".
-- iCal doesn't currently implement this, but there's a hook for it in the script. :)
set weekdayCode to text -2 thru -1 of BYDAY
if ((count BYDAY) > 2) then
set instanceNos to {(text 1 thru -3 of BYDAY) as integer}
else
set instanceNos to {1, 2, 3, 4, -1}
end if
return {weekdayCode, instanceNos}
end if
end getWeekdayInstanceStuff
(* Get a date that's m calendar months after (or before, if m is negative) the input date. *)
to addMonths(oldDate, m)
copy oldDate to newDate
set {y, m} to {m div 12, m mod 12}
set newDate's year to (newDate's year) + y
-- Add the odd months (at 32 days per month) and set the day.
if (m is not 0) then tell newDate to set {day, day} to {32 * m, day}
-- If the day's changed, the original doesn't exist in the target month.
-- Subtract the overflow into the following month to return to the last day of the target month.
if (newDate's day is not oldDate's day) then set newDate to newDate - (newDate's day) * days
return newDate
end addMonths
(* Get the date of a given instance of a given weekday in the month of a given date. *)
on getWeekdayDate(givenDate, weekdayCode, instanceNo) -- (AS date, 2-letter BYDAY code, integer)
-- Get a date in the past that's known to have the required weekday. (Sunday 5th January 1000 + 0 to 6 days.)
set refDate to («data isot313030302D30312D3035» as date) + (offset of weekdayCode in "SUMOTUWETHFRSA") div 2 * days
-- Get the last day of the seven-day period in the current month that includes the given instance of any weekday.
if (instanceNo is -1) then
tell givenDate to tell it + (32 - (its day)) * days to set periodEnd to it - (its day) * days -- Last day of month.
else
copy givenDate to periodEnd
set periodEnd's day to instanceNo * 7 -- 7th, 14th, 21st, or 28th of month.
end if
-- Round down to an exact number of weeks after the known-weekday date.
return periodEnd - (periodEnd - refDate) mod weeks
end getWeekdayDate
(* -- Demo code:
tell application "iCal"
set {allEvents, allSummaries, allStartDates, allRecurrenceTexts} to {it, summary, start date, recurrence} of events of calendar "My Calendar"
end tell
set today to (current date)
repeat with i from 1 to (count allEvents)
set nextRecurrence to my getNextRecurrence(item i of allRecurrenceTexts, item i of allStartDates, today)
if (nextRecurrence is missing value) then
-- Item i of allEvents, whose summary is item i of allSummaries, has expired.
else if (nextRecurrence is today) then
-- The event recurs today.
else
-- The event will recur on the date contained in nextRecurrence.
end if
end repeat *)