Sunday, August 04, 2013

Using AppleScript and/or Python to create a plaintext list of upcoming events from OS X Calendar.app

[This post starts with the general problem and some basic scripts -- but don't worry -- it ends with two definitive solutions.]

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:
I was able to retrieve the references and names of all my Google Calendars using 2005 code from MacTech

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

Spotlight Calendar searches in Lion and Mountain Lion - kind of weird

In Lion and Mountain Lion Apple gave this Spotlight search example:

Except I tried in Lion and Mountain Lion and that search string didn't do anything whereas iOS 6 Siri returns a list of meetings for 'tomorrow' (and comments first that I have "quite a lot of them, which is funny).
 
A partial fix  is to use the Events Keyword (see also: Mac 101: Use Spotlight for quick review of Calendar events)

Calendar events

kind:event

kind:events

Alas, when I tested this is Spotlight and Calendar search things got weird. I think because all of my Calendars are synchronized from Gmail/Google I exposed some Spotlight bugs. At first searching for Kind:event would only display "Birthdays" (From Facebook?), but then I added a single Event on my iCloud Calendar (no way to create a purely local Calendar any more?). That seemed to awaken Spotlight and soon kind:even generated a LOT of hits from ALL my Calendars (same as searching on the '.' (dot) operator - old trick post Agenda removal).
 
Then I tried date operators while searching within Calendar.app

kind:event date:today

That returned an event from over a year ago (7/5/2012). Ok, that's bizarre.
 
So then I tried a date range using spotlight as a guide

kind:event date:6/29/13-7/25/13

That returned the same person's Facebook birthday, but from 2013 and 2014.
 
Then I tried the same search string in Spotlight, and I got a funny mixture of Calendars back.
 
So then I filtered out all the calendars I didn't want and I showed that either the old '.' or the newer Kind:Event search in Calendar.app would return a 10.5 style event list. Still, i couldn't get a date operator to work in Calendar.app search though -- it just interpreted it as a string (so 8/5/2013 worked to show events on that date, but 8/5/13 didn't work). The OS X date:tomorrow documentation didn't work.
 
So I hit a wall on this project, but I figure its worth sharing my experience for anyone looking further. If nothing else the links may be useful, and it was interesting to learn why Lion tips on changing days displayed in week view no longer work.
 
Editorial Comment: In my work day on Win 7 and Outlook 2007, I routinely use Calendar searches like <after:today Fred>. Apple's inability to support this kind of basic operation in their Calendar.app isn't a good sign.
 
See also

Saturday, August 03, 2013

How to get a reasonable plaintext listing of calendar events using Google Calendar or OS X Calendar.app

I despise Outlook as much as any other geek, but, damn, there are things I miss when I'm on my Mac with Google Calendar and/or Mountain Lion Calendar.

To put it delicately, calendaring on the Mac is a flaming wad of misery. Google is only a little better. Simple tasks, like a plaintext agenda view one's Calendar, are mysterious or impossible on the modern Mac. (Classic had much better Calendar tools.)

Here's one way to get a plaintext agenda list from Google Calendar.

  1. From Calendar list go to Calendar Settings.
  2. In Calendar Details click on HTML to get web view of your calendar. Go to Agenda view and you can now select a range of dates and paste into your preferred text editor.

For extra points, you can use the top-secret multiple-calendar web sharing feature and get events from more than one calendar.

Update 8/4/13

In Calendar.app Day view you can:

  • Select Calendar
  • Click mini-calendar for start date
  • Click on an event at start of that day
  • Click mini-calendar for end date
  • Shift-click on event at end
  • Paste into text editor

The result is pretty much the same as using Google Calendar.

Or you can search on either '.' or "Kind:Event" in Calendar and select a range from the search results. The '.' search works in Google Calendar too, though in 10/2020 it gCal mysteriously omits some dates from some feeds.

Update 8/4/13b:

iCalBuddy is a terminal app that can "get lists of events and tasks/to-do's from the OS X calendar database"; results can be displayed on the desktop. It was very recently updated, so should be fine on Mountain Lion.

Thursday, August 01, 2013

Kobo Glo: Mini-Review

After much deliberation, and on the advice of @la and other appnetizens, I bought a Kobo Glo from, oddly enough, Common Good Books.

I ended up with the Kobo Glo largely because I can't quite persuade myself to buy the current generation of iPads, my experience with the original Nexus 7 was disappointing, and I assume the Nexus 7 Retina will have awful battery life. At the same time I wanted an ePUB reader for compatibility with iBooks.app and Google Play, and because Adobe's DRM can be removed. I also wanted simplicity of mounting/charging and transferring files via USB, and the expansion of a (micro) SD card option.

So I was left with buying either an eInk Nook or Kobo. I disliked the Nook because the one I tried was misconfigured to do an eInk full screen refresh every page; I was left with an unfair impression. It helped that while is US presence is slight the Kobo seems to have a good international market -- and Kobo's marketing angle is 'freedom' (though I think they use the same Adobe DRM as Google Play and Nook).

I paid $130 for the Glo plus tax. No bargains this time around; it seems to cost the same online. It comes in a compact box with a slender setup guide and a micro-USB cable (no charger) and no slip case or cover.

My initial experience was not good. I connected it to my Mac and ran the setup using Kobo Desktop, which seems to be a well behaved and digitally signed Mac app. After initialization an update started, but it behaved a bit oddly. I had to pull the USB cable to get the update to commence, but then I feared power loss and reinserted the cable. Although it seemed to behave and register normally, the Kobo didn't recognize any of the files I put on it via USB desktop mounting. It also, I later realized, was not showing the proper 'connect' dialog on USB cable connection.

So next I did a factory refresh, and this time instead of a desktop setup I did a WiFi connection setup. The device updated again, but this time it took far longer for the update to continue. It seemed to be stuck, cycling between progress indicators for minutes -- but I ignored it and, somewhat to my surprise, it eventually restarted. I completed my registration using the painful onscreen keyboard.

After the refresh and registration I was able to drag files to the Kobo and they were processed on device. I'd forgotten how compact ePubs are, and how big 2GB is. At 1MB or so per ePUB it will take a long time to use up my remaining 1.35 GB -- even if I don't use the micro-SD card slot.

My initial impressions are guarded; I think the iPad Mini Retina will be a better reading experience -- but it will cost far more and it's not yet available. My iPhone 5 has a better screen, but it's small for reading technical books. The mandatory eInk full page flicker, which occurs at least every 6 pages, is annoying.

On the other hand, the battery life is at least weeks long. It is a boring black and white device, so I don't have to worry about the kids fighting for a turn. When I buy my iPad Mini it will still be a useful device for someone else.

For the moment I carry it in a cloth pouch, I'll see how the plastic screen holds up.

See also: