Google Calendar and its API

Until yesterday I'd only tire-kicked Google Calendar. I couldn't use it for real until I loaded it with real data. Last night I finally got around to doing that. In my case, the export/import path led from Mozilla Calendar to an XML representation of iCalendar format to Atom and then into Google Calendar. The hero of the piece is Joe Gregorio, whose wonderful Sparkline service I highlighted a while back. Joe is also, and rather more notably, the author of both the Atom Publishing Protocol and Python's httplib2 library, two projects that came together to facilitate my transfer of calendar data (see script below).

Google's Patrick Chanezon had alerted me to the fact that Joe has added a new authentication scheme to httplib2. Along with HTTP Basic, Digest, and a couple of others, this Python HTTP library will now handle Google-style authentication. That's really the only tricky thing about using Google Calendar's API. Everything else is URLs and Atom entries. There are Java and C# wrappers for this stuff, but I'm having a ball just using Python's interactive mode to explore the Calendar API. Among the things I can easily do: search for entries matching dentist, search for entries after June 10, receive the results of any query as an Atom or RSS feed.

Most discussion of Gcal has (appropriately) focused on its user interface, which puts many a conventional fat client to shame in terms of both its responsiveness and its ease of use. From my perspective, though, what matters equally is an API that's powerful, flexible, and easy to use. With the still-unsupported libgmail API, for example, I've illlustrated how easy it can be to issue a complex Gmail query, gather all the messages in the found threads, and combine them. This is just one of a million intensely practical real-world scenarios which, I hope, will at some point be officially supported by Gmail in the same way they now are by Gcal.

Last week I pitched a hissy fit when Google PR stomped on my inquiry about GData, which is the RESTful, RSS-and-Atom-oriented architecture of which the Gcal API is the first concrete manifestation. This week I want to show what can happen when communication and collaboration flow as they naturally can and should. Google's instincts about web architecture are spot on. Sort things out on the developer relations front, and it'll be amazing.


# import Mozilla Calendar .xcs into Google Calendar
 
import httplib2, getpass, traceback, sys, libxml2, re
  
pwd = getpass.getpass('pwd: ')
h = httplib2.Http()
h.add_credentials(USERNAME,pwd)
h.follow_all_redirects = True
uri = 'http://www.google.com/calendar/feeds/USERNAME@gmail.com/private/full'
 
#2006-05-30T18:00:00.000Z
#20041207T005046Z
def formatDateTime(datetime):
  m = re.match('(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z',datetime)
  try:
    return "%s-%s-%sT%s:%s:%s.000Z" % m.groups()
  except:
    return datetime
 
def dictFromEventNode(eventNode):
  title = eventNode.xpathEval('./summary')[0].content
  try:
    descr = eventNode.xpathEval('./description')[0].content
  except:
    descr = ''
  start = eventNode.xpathEval('./dtstart')[0].content
  start = formatDateTime(start)
  try:
    end = eventNode.xpathEval('./dtend')[0].content
  except:    
    end = start
  end = formatDateTime(start)
  return {'title':title,'descr':descr,'where':'','start':start,'end':end}
 
def atomEntryFromDict(d):
  entry = """
<entry xmlns='http:\//www.w3.org/2005/Atom'
    xmlns:gd='http:\//schemas.google.com/g/2005'>
  <category scheme='http:\//schemas.google.com/g/2005#kind'
    term='http:\//schemas.google.com/g/2005#event'></category>
  <title type='text'>%s</title>
  <content type='text'>%s</content>
  <author>
    <name>NAME</name>
    <email>USERNAME@gmail.com </email>
  </author>
  <gd:transparency
    value='http:\//schemas.google.com/g/2005#event.opaque'>
  </gd:transparency>
  <gd:eventStatus
    value='http:\//schemas.google.com/g/2005#event.confirmed'>
  </gd:eventStatus>
  <gd:where valueString='%s'></gd:where>
  <gd:when startTime='%s'
     endTime= '%s'>
  </gd:when>
</entry> """ % (d['title'],d['descr'],d['where'],d['start'],d['end'])
  return entry
 
def insertGcal(atomEntry):
  try:
    headers = {'Content-Type': 'application/atom+xml'} 
    resp,content = h.request(uri,"POST",body=atomEntry,headers=headers)
    assert ( resp['status'] == '201' )
  except:
    traceback.print_exc(None, sys.stderr)
     
xml = libxml2.parseFile('mozcal.xml')
 
events = xml.xpathEval('\//vevent')
for event in events:
  dict = dictFromEventNode(event)
  print dict['title']
  entry = atomEntryFromDict(dict)
  insertGcal(entry)

Former URL: http://weblog.infoworld.com/udell/2006/06/13.html#a1468