Monday, September 21, 2015

Your Appointments, Sir


No matter how careful you are about putting stuff into your calendar you still have to look at it to see where you're supposed to be. I had the same problem, but not anymore, because I figured out how to have my Mac read my appointments to me out loud, every day, on a timer. It's very cool, and since it's scheduled to run every morning at 7:30 AM, I think it's going to help me out quite a bit. Especially on weekends, when I tend to forget to check the calendar.

I was hoping to do this with an Automator Workflow, and if not for an iCal bug Automator would have been the way to go. I worked pretty hard at making it work with Automator but eventually realized that the reason it didn't work was something I couldn't work around. So I turned to AppleScript.

Here's the script. The gray parts are comments, put there to help you understand what's going on. Note: the script gets you part of the way there. You still need something to trigger the script at the appropriate time. Lots of programs can do that for you. I chose Script Timer, a nice little $12 program that I just found out about. Here's the link. You can get a free 30-day trial.


set the_text to ""
set today to current date
set time of today to 0
set tomorrow to (today) + 86400 -- seconds
--
-- Here are the calendars I want to check. Yours will be different. Change the following line to match the names of your calendars.
-- If you are going to check ALL of your calendars this script could be simplified. Send me an email and I'll help you.
-- Christian Boyce, macman@ioperating.com
set the_calendar_list to {"CB & A", "usc football 2009", "Cal Football 2009", "Texas Football 2009", "UCLA Football 2009", "Birthdays"}
--
tell application "iCal"
-- First we need to tell iCal which calendars are going to be checked. We match the names in "the_calendar_list" to the names of the actual calendars in iCal. The ones that match are added to our "the_calendars" list.
--
set the_calendars to {}
set every_calendar to every calendar
-- Now we have a list of calendars to check.
repeat with an_item in the_calendar_list
set end of the_calendars to (first calendar whose name is an_item)
end repeat
-- Now we check, on a calendar by calendar basis, for appointments on the current day.
repeat with a_calendar in the_calendars
tell a_calendar
set the_events to (every event whose start date > today and start date < tomorrow)
--
-- Here we sort the list of events for the day. If we don't do this they won't be chronological. iCal sorts them in creation order unless we run this little "sortEvents" routine.
--
set the_events to my sortEvents(the_events)
-- Now we have a sorted list. Let's create a string for the Mac to speak. Loop through the events and make that string.
set i to 1
repeat with an_event in the_events
set x to properties of an_event
set the_summary to summary of an_event
set the_start_date to start date of an_event
set the_end_date to end date of an_event
set the_start_time to time string of the_start_date
set the_end_time to time string of the_end_date
--
set the_text to the_text & return & "Appointment number" & i & "." & return & the_start_time & " to " & the_end_time & "." & return & summary of an_event & return & return
set i to i + 1
end repeat
end tell
end repeat
--
-- If there aren't any events the string "the_text" will be empty. In that case we want to say something different.
if the_text is "" then
set the_text to "Good morning." & return & "Today is " & date string of (current date) & return & return & "Unfortunately, you have no appointments today."
else
set the_text to "Good morning." & return & "Today is " & date string of (current date) & return & return & "Here are today's appointments." & return & return & the_text & return & "That was the last appointment for today."
end if
end tell
--
-- This is neat: I want to set the volume loud enough for me to hear it, and then set the volume back to where it was before I adjusted it.
set old_volume to output volume of (get volume settings)
set volume output volume 60
say the_text using "Alex"
set volume output volume (old_volume)
--
-- This is the sorting subroutine. I found it on MacScripter.net.
on findLeastItem(lst)
tell application "iCal"
set theLeast to start date of item 1 of lst
set theIndex to 1
set iterater to 1
repeat with i in lst
if start date of i theLeast then
set theLeast to start date of i
set theIndex to iterater
end if
set iterater to iterater + 1
end repeat

return theIndex
end tell
end findLeastItem

on removeItemAtIndex(lst, theIndex)
set newList to {}
set theLength to length of lst
if theLength = 1 then
set newList to {}
else if theLength = theIndex then
set newList to items 1 thru (theLength - 1) of lst
else if theIndex = 1 then
set newList to items 2 thru theLength of lst
else
set newList to items 1 thru (theIndex - 1) of lst & items (theIndex + 1) thru (theLength) of lst
end if
return newList
end removeItemAtIndex

on sortEvents(myList)
set myNewList to {}
repeat until length of myList = 0
set leastIndex to findLeastItem(myList)
set end of myNewList to item leastIndex of myList
set myList to removeItemAtIndex(myList, leastIndex)
end repeat
return myNewList
end sortEvents

any question? enter your email below and I will help you