Yesterday I described several clumsy hacks to get a plaintext display of upcoming Calendar events from one or more Mac or Google Calendars. In today's post I look at what's possible with AppleScript [1] (or Python and AppScript) support -- though as of 8/4/13 I'm not done with it.
Specifically I want to get a plaintext list of events on one or more Google Calendars through AppleScript and Calendar.app when Calendar.app is configured to read Calendars from a Google share. Based on some articles I found, it looks possible:
- MacTech Introduction to Scripting iCal 2005 - this is the essential reference
- AppleScript: Print daily iCal agenda | TUAW 2008
- Getting today's events from iCal with Applescript - Stack Overflow 5/2011
- Applescript to find events in iCal - Stack Overflow 2010
- MacScripter iCal articles (may need to login for this tag link to work, big set of examples including tutorials:
- MacScripter / iCal - trigger script and act programmatically on event or todo (designed to send alarms to growl, walks all calendars)
- MacScripter / iCal Alarms!
- MacScripter / Appointments, Alarms & To Dos in iCal
tell application "iCal"
set theCalendars to every calendar
end tell
and
tell application "iCal"
set theCalendarNames to title of every calendar
end tell
I was also able to view a calendar at a specified date and I found it used the selected calendars though not in my desired format.
This script listed events by event id for the current date and it seems easy to modify if I study some AppleScript date arithmetic ...
set {year:y, month:m, day:d} to current date
set str to (m as string) & " " & (d as string) & " " & (y as string)
set today to date str
set tomorrow to today + 60 * 60 * 24
tell application "iCal"
tell calendar "Lotus Notes"
set curr to every event whose start date is greater than or equal to today ¬
and start date is less than or equal to tomorrow
end tell
end tell
This one worked, but search can take a very long time...
tell application "iCal"
tell calendar "Domestic"
set theEventList to every event whose summary contains "Recycling"
end tell
set theEvent to first item of theEventList
return summary of theEvent
end tell
I'll have to leave this task for a bit, but I have a long family car drive coming up and I might be able to play with it on the way. I've also asked about this on MacScripter.net, where respondents have been known to solve problems very thoroughly.
Or I could just try Automator...Using Automator to create a plaintext list of upcoming events from OS X Calendar.app
- fn -
[1] SQL, AppleScript and COBOL all have one thing in common - they were designed for use by "non-programmers". Let that be a lesson.
See also
- Top 10 Calendar Apps For Mac 2010. PreMinder is still around, but not in the App Store.
- Gordon's Tech: Spotlight Calendar searches in Lion and Mountain Lion - kind of weird
- Gordon's Tech: How to get a reasonable plaintext listing of calendar events using Google Calendar or OS X Calendar.app
- Using Automator to create a plaintext list of upcoming events from OS X Calendar.app
- iCalDoit - automating iCal (alas, no Mountain Lion support yet ...)
Update 8/5/13: Nigel Garvey, a moderator at the extraordinary and venerable MacScripter site, did a professional version of this script. It's working quite well for me, though it only works with one Calendar. It's easy to tweak the text result. (See post for his comments.)
-- 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 and summaries which are in the given date range.on filterToDateRange(theStartDates, theSummaries, dateRangeStart, dateRangeEnd)
set {eventDatesInRange, eventSummariesInRange} to {{}, {}}
repeat with i from 1 to (count theStartDates)
set thisStartDate to item i of theStartDates
if (not ((thisStartDate comes before dateRangeStart) or (thisStartDate comes after dateRangeEnd))) then
set end of eventDatesInRange to thisStartDate
set end of eventSummariesInRange to item i of theSummaries
end if
end repeat
return {eventDatesInRange, eventSummariesInRange}
end filterToDateRange
-- Sort both the start-date and summary lists by start date.on sortByDate(eventDatesInRange, eventSummariesInRange)
-- A sort-customisation object for sorting the summary list in parallel with the date list. script custom
property summaries : eventSummariesInRange
on swap(i, j)
tell item i of my summaries
set item i of my summaries to item j of my summaries
set item j of my summaries to it
end tell
end swap
end script
CustomBubbleSort(eventDatesInRange, 1, -1, {slave:custom})
end sortByDate
-- CustomBubbleSort from "A Dose of Sorts" by Nigel Garvey.-- The number of items to be sorted here is likely to be small.on CustomBubbleSort(theList, l, r, customiser)
script o
property comparer : me
property slave : me
property lst : theList
on bsrt(l, r)
set l2 to l + 1
repeat with j from r to l2 by -1
set a to item l of o's lst
repeat with i from l2 to j
set b to item i of o's lst
if (comparer's isGreater(a, b)) then
set item (i - 1) of o's lst to b
set item i of o's lst to a
slave's swap(i - 1, i)
else
set a to b
end if
end repeat
end repeat
end bsrt
-- Default comparison and slave handlers for an ordinary sort. on isGreater(a, b)
(a > b)
end isGreater
on swap(a, b)
end swap
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 bsrt(l, r)
end if
return -- nothing end CustomBubbleSort
-- Compose the text from the items in the start-date and summary lists.on composeText(eventDatesInRange, eventSummariesInRange)
set txt to ""
set gap to linefeed & linefeed
repeat with i from 1 to (count eventDatesInRange)
set txt to txt & (date string of item i of eventDatesInRange) & (linefeed & item i of eventSummariesInRange & gap)
end repeat
return text 1 thru -3 of txt
end composeText
on main()
tell application "iCal" to set {theStartDates, theSummaries} to {start date, summary} of events of calendar "FL Family Calendar"
set {dateRangeStart, dateRangeEnd} to getDateRange()
set {eventDatesInRange, eventSummariesInRange} to filterToDateRange(theStartDates, theSummaries, dateRangeStart, dateRangeEnd)
sortByDate(eventDatesInRange, eventSummariesInRange)
set txt to composeText(eventDatesInRange, eventSummariesInRange)
tell application "TextEdit"
make new document with properties {text:txt}
activate
end tell
end main
main()
Update 8/6/13: Clark Goble codes up comingevents.py, which uses PyObjC, AppScript, Osax and, of course, Python. It includes Parsedatetime so date entry can be free text and outputs plain text, HTML, Clipboard and probably sends a message to another dimension. I think at this point we've got scripted plaintext event publication covered. Be sure not to miss his links to related posts.
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 and summaries which are in the given date range.on filterToDateRange(theStartDates, theSummaries, dateRangeStart, dateRangeEnd)
set {eventDatesInRange, eventSummariesInRange} to {{}, {}}
repeat with i from 1 to (count theStartDates)
set thisStartDate to item i of theStartDates
if (not ((thisStartDate comes before dateRangeStart) or (thisStartDate comes after dateRangeEnd))) then
set end of eventDatesInRange to thisStartDate
set end of eventSummariesInRange to item i of theSummaries
end if
end repeat
return {eventDatesInRange, eventSummariesInRange}
end filterToDateRange
-- Sort both the start-date and summary lists by start date.on sortByDate(eventDatesInRange, eventSummariesInRange)
-- A sort-customisation object for sorting the summary list in parallel with the date list. script custom
property summaries : eventSummariesInRange
on swap(i, j)
tell item i of my summaries
set item i of my summaries to item j of my summaries
set item j of my summaries to it
end tell
end swap
end script
CustomBubbleSort(eventDatesInRange, 1, -1, {slave:custom})
end sortByDate
-- CustomBubbleSort from "A Dose of Sorts" by Nigel Garvey.-- The number of items to be sorted here is likely to be small.on CustomBubbleSort(theList, l, r, customiser)
script o
property comparer : me
property slave : me
property lst : theList
on bsrt(l, r)
set l2 to l + 1
repeat with j from r to l2 by -1
set a to item l of o's lst
repeat with i from l2 to j
set b to item i of o's lst
if (comparer's isGreater(a, b)) then
set item (i - 1) of o's lst to b
set item i of o's lst to a
slave's swap(i - 1, i)
else
set a to b
end if
end repeat
end repeat
end bsrt
-- Default comparison and slave handlers for an ordinary sort. on isGreater(a, b)
(a > b)
end isGreater
on swap(a, b)
end swap
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 bsrt(l, r)
end if
return -- nothing end CustomBubbleSort
-- Compose the text from the items in the start-date and summary lists.on composeText(eventDatesInRange, eventSummariesInRange)
set txt to ""
set gap to linefeed & linefeed
repeat with i from 1 to (count eventDatesInRange)
set txt to txt & (date string of item i of eventDatesInRange) & (linefeed & item i of eventSummariesInRange & gap)
end repeat
return text 1 thru -3 of txt
end composeText
on main()
tell application "iCal" to set {theStartDates, theSummaries} to {start date, summary} of events of calendar "FL Family Calendar"
set {dateRangeStart, dateRangeEnd} to getDateRange()
set {eventDatesInRange, eventSummariesInRange} to filterToDateRange(theStartDates, theSummaries, dateRangeStart, dateRangeEnd)
sortByDate(eventDatesInRange, eventSummariesInRange)
set txt to composeText(eventDatesInRange, eventSummariesInRange)
tell application "TextEdit"
make new document with properties {text:txt}
activate
end tell
end main
main()
Update 8/6/13: Clark Goble codes up comingevents.py, which uses PyObjC, AppScript, Osax and, of course, Python. It includes Parsedatetime so date entry can be free text and outputs plain text, HTML, Clipboard and probably sends a message to another dimension. I think at this point we've got scripted plaintext event publication covered. Be sure not to miss his links to related posts.