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@christianboyce.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


by


9 thoughts on “Your Appointments, Sir

  1. UPDATE: I figured out a faster way to get the calendars. All one has to do is this:

    set the_calendars to every calendar whose name contains "football" or name is "NFL PrimeTime 2011" or name is "CB & A"

    This gets the list of calendars without that silly looping that I'd been doing before. So…

    Replace this:
    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

    with this:
    set the_calendars to every calendar whose name contains "football" or name is "NFL PrimeTime 2011" or name is "CB & A"

    and away you go. Of course you would modify the above to choose the calendars you like. Notice my use of "contains" for the calendars that have "football" in their names. That's because I have a calendar called "UCLA football schedule" and another called "Texas football schedule" and so on. This way, I get all of those without explicitly specifying them in the script.

  2. I've tried your program and it doesn't seem to be picking up my appointment. I changed the calendars to the ones that I have for mine. But it keeps saying I have no appointments. When I do. I'm not sure what the problem is. Can you help.

  3. Hey Anonymous, contact me directly and I will help you out. Meanwhile, I wonder whether your calendars have only repeating events. If so, we're sunk, as I have not figured out a way to get a repeating event to be picked up by AppleScript. (The problem is, a repeating event is seen by AppleScript as starting on the day where the event first appears. All subsequent occurrences are invisible to AppleScript, as if they are not there at all.)

    Use the "Email Me" link at the top of the page and let's figure this out.

  4. Hello,

    Very cool stuff. I was wondering if you or anyone else has figured out away to have repeated events spoken aloud.

    Thanks,

    Travis

      1. Thanks for the prompt reply. The CalendaLib looks pretty interesting, I’ll give it a shot. In the mean time, for anyone wanting to get the functionality of the original script by Boyce, I found that I can highlight all events for the week, use “ctrl x” to cut and “ctrl v” to paste and it seems to remove the recurrence label without totally removing the event. Prelim test suggest this will be effective as a short-term work around with the main drawback of having to do that on a weekly basis. Also, note I was trying this out on events created in a gmail account that I have synced to mac Calendar app.

        Cheers

        1. You’re welcome. Be sure you’re using the modification I made for getting the calendars– it’s in the comment here, from September 2011. Let me know if you need more help with this.

Leave a Reply

Your email address will not be published.