I wouldn’t describe collating the event data from several calendars by calendar date and then formatting the output text as being particularly similar…
The version below has only been tested in El Capitan. Instead of sorting a single calendar’s event summaries in parallel with the associated start dates, it collates the data from all the calendars into a list of lists ” each sublist containing the start date, summary, and calendar of an event. This list is then sorted by the sublists’ start dates. A new document is then created in TextEdit and text representing the events is output to it as it’s derived from the sorted data.
With more calendars involved, there’ll usually be more events to process, so I’ve used script object referencing in the list collation and replaced the bubble sort with a shell sort. However, most of the running time will be the initial wait for Calendar to return the data to the script, which could take up to a minute if there are a lot of events in the calendars. I think there are faster ways to get the data using ASObjC, but I haven’t got round to learning them or it yet.
-- Tested with Calendar 8.0 and TextEdit 1.11 (new documents defaulting to RTF) in Mac OS 10.11.2
-- Ask the user for the range of dates to be covered.
on getDateRange()
set today to (current date)
set d1 to today's short date string
set d2 to short date string of (today + 6 * days)
set dateRange to text returned of (display dialog "Enter the required date range:" default answer d1 & " - " & d2)
set dateRangeStart to date (text from word 1 to word 3 of dateRange)
set dateRangeEnd to date (text from word -3 to word -1 of dateRange)
set dateRangeEnd's time to days - 1 -- Sets the last date's time to 23:59:59, the last second of the range.
return {dateRangeStart, dateRangeEnd}
end getDateRange
-- Return the start dates, summaries, and calendar names of events in the given date range.
-- {{start date, summary, calendar name}, {start date, summary, calendar name}, . }
on filterToDateRange(theStartDates, theSummaries, theCalendarNames, dateRangeStart, dateRangeEnd)
script o
property sDates : theStartDates
property summaries : theSummaries
property cNames : theCalendarNames
property filteredData : {}
end script
repeat with i from 1 to (count theCalendarNames)
set o's sDates to item i of theStartDates
set o's summaries to item i of theSummaries
set thisCalendarName to item i of theCalendarNames
repeat with j from 1 to (count o's sDates)
set thisStartDate to item j of o's sDates
if (not ((thisStartDate comes before dateRangeStart) or (thisStartDate comes after dateRangeEnd))) then
set thisSummary to item j of o's summaries
set end of o's filteredData to {thisStartDate, thisSummary, thisCalendarName}
end if
end repeat
end repeat
return o's filteredData
end filterToDateRange
-- Sort the filtered data by start date.
on sortByDate(filteredData)
-- A sort-customisation object which compares the first items of two sublists taken from the list being sorted.
script custom
on isGreater(a, b)
return (beginning of a > beginning of b)
end isGreater
end script
CustomShellSort(filteredData, 1, -1, {comparer:custom})
end sortByDate
-- Shell sort. Algorithm: Donald Shell, 1959. AppleScript implementation: Nigel Garvey, 2010.
on CustomShellSort(theList, l, r, customiser)
script o
property comparer : me
property slave : me
property lst : theList
on shsrt(l, r)
set step to (r - l + 1) div 2
repeat while (step > 0)
slave's setStep(step)
repeat with j from (l + step) to r
set v to item j of o's lst
repeat with i from (j - step) to l by -step
tell item i of o's lst
if (comparer's isGreater(it, v)) then
set item (i + step) of o's lst to it
else
set i to i + step
exit repeat
end if
end tell
end repeat
set item i of o's lst to v
slave's rotate(i, j)
end repeat
set step to (step / 2.2) as integer
end repeat
end shsrt
-- Default comparison and slave handlers for an ordinary sort.
on isGreater(a, b)
(a > b)
end isGreater
on rotate(a, b)
end rotate
on setStep(a)
end setStep
end script
-- Process the input parameters.
set listLen to (count theList)
if (listLen > 1) then
-- Negative and/or transposed range indices.
if (l < 0) then set l to listLen + l + 1
if (r < 0) then set r to listLen + r + 1
if (l > r) then set {l, r} to {r, l}
-- Supplied or default customisation scripts.
if (customiser's class is record) then set {comparer:o's comparer, slave:o's slave} to (customiser & {comparer:o, slave:o})
-- Do the sort.
o's shsrt(l, r)
end if
return -- nothing.
end CustomShellSort
-- Create a new TextEdit document with text derived from the gathered data.
on composeText(filteredData)
tell application "TextEdit"
-- Make a new document, with a minimal text so that we can discover the name of its font.
set newDoc to (make new document with properties {text:" "})
set baseFont to font of newDoc's text
-- This ASSUMES that an equivalent bold font exists and that its name is the same as the plain font with " bold" appended.
set boldFont to baseFont & " bold"
-- Dummy text no longer needed.
set newDoc's text to ""
activate
end tell
if (filteredData is {}) then
-- If no events have been discovered in the date range, print that fact.
tell application "TextEdit" to make new paragraph at end of newDoc's text with data "No events found in this period." with properties {font:boldFont}
else
-- Otherwise print the event details.
set currentCalendarDate to "" -- The calendar date currently being processed. (None yet.)
repeat with i from 1 to (count filteredData)
-- Get the data for an event from the list of filtered data.
set {{date string:thisCalendarDate, hours:thisStartTimeH, minutes:thisStartTimeM}, thisSummary, thisCalendar} to item i of filteredData
-- If the calendar date is different from the one we've been processing up till now, output an empty line and the new date string to TextEdit.
if (thisCalendarDate is not currentCalendarDate) then
tell application "TextEdit"
make new paragraph at end of newDoc's text with data linefeed with properties {font:baseFont}
make new paragraph at end of newDoc's text with data (thisCalendarDate & linefeed) with properties {font:boldFont}
end tell
-- Make the new date the one currently being processed.
set currentCalendarDate to thisCalendarDate
end if
-- Create a 24-hour time string from the hours and minutes of the start date.
tell (10000 + thisStartTimeH * 100 + thisStartTimeM) as text to set thisStartTime to text 2 thru 3 & ":" & text 4 thru 5
-- Output the entry for this event to TextEdit.
tell application "TextEdit" to make new paragraph at end of newDoc's text with data ("\"" & thisCalendar & "\" calendar: " & thisStartTime & " " & thisSummary & linefeed) with properties {font:baseFont}
end repeat
end if
end composeText
on main()
say "Getting data from Calendar. It may take a while."
tell application "Calendar" to set {{theStartDates, theSummaries}, theCalendarNames} to {{start date, summary} of events, name} of calendars
set {dateRangeStart, dateRangeEnd} to getDateRange()
set filteredData to filterToDateRange(theStartDates, theSummaries, theCalendarNames, dateRangeStart, dateRangeEnd)
sortByDate(filteredData)
composeText(filteredData)
end main
main()