Nick (nickmurdoch) wrote,
Nick
nickmurdoch

Autoview .ics iCalendar (text/calendar) attachments in mutt

We interrupt this journal to bring you an important productivity-related update!

I've been vaguely trying to figure out how to display text/calendar ical meeting invite attachments in mutt, which I receive from colleagues sending requests from their fancy-pants graphical email clients. If you've looked at a vCalendar/iCalendar file you'll know it's not a very pleasant thing to read, especially since Outlook seems to like sending invites relative to Eastern Time from and to people located in the UK.

Outlook used to send a text summary along with the attachment so this hasn't been particularly high on my priority lists until lately, now that both the iPhone and the latest versions of Outlook simply send a blank message (in both text and HTML parts!) with the invite attached.

I found a collection of Ruby scripts after a few attempts to get my head around a couple of Python vCalendar-parsing libraries without much luck. The viewical.rb file included in that package worked fairly well, but had a rounding error causing HH:30 to turn into HH:29 and didn't compensate for Outlook insanity, so I've fixed that (at least, during daylight savings, we'll have to see what happens after that). The package from the page above also provides some utilities to Accept/Decline meeting invites, but I don't have any personal use for them at the moment so I haven't set them up. My copy of viewical.rb now looks like this:

#!/usr/bin/env ruby

require "rubygems" # apt-get install rubygems
require "icalendar" # gem install icalendar
require "date"

class DateTime
  def myformat
    (self.offset == 0 ? (DateTime.parse(self.strftime("%a %b %d %Y, %H:%M ") + self.icalendar_tzid)) : self).
        new_offset(Rational(Time.now.utc_offset - 60*60, 24*60*60)).strftime("%a %b %d %Y, %H:%M")
        # - 60*60 to compensate for icalendar gem/Outlook mismatch
  end
end

cals = Icalendar.parse($<)
cals.each do |cal|
  cal.events.each do |event|
    puts "Organizer: #{event.organizer}"
    puts "Event:     #{event.summary}"
    puts "Starts:    #{event.dtstart.myformat} local time"
    puts "Ends:      #{event.dtend.myformat}"
    puts "Location:  #{event.location}"
    puts "Contact:   #{event.contacts}"
    puts "Description:\n#{event.description}"
    puts ""
    end
end


Put that in your PATH somewhere, then put this in your .mailcap:

text/calendar; icalview.rb; copiousoutput

And this in your .muttrc:

auto_view text/calendar

Now whenever you view an email with a text/calendar attachment, mutt will display it in-line where the message usually appears (above the message, if there is one).

As an aside, it was frustrating to find out that although Google Calendar has an "Import Calendar" function on their frontend, there's no equivalent call in their API, so my idea to write an application to just pump the attachment directly to my Google Calendar account fell rather flat on its face.

Edit (2016-02-18): A more recent version of the Ruby icalendar package breaks the above script, and the time zone stuff is still broken in it anyway. In the meantime it looks like the offerings from the Python side of things have improved. The ics package seems to parse the files really well, including timezone information using the arrow library. I'm impressed. Here's the Python equivalent of the ruby script:

icalview.py
#!/usr/bin/env python
import sys

import ics # pip install ics
import arrow # pulled in with ics, else pip install arrow

cal = ics.Calendar(sys.stdin.read().decode('utf-8'))

tz = arrow.now().tzinfo
tzformat = 'ddd DD MMM HH:mm'

for event in cal.events:
    extra = {}
    for item in event._unused:
        if not isinstance(item, ics.parse.ContentLine):
            continue
        try:
            extra[item.name.lower()] = item.value
        except:
            print item
            raise
    print "Event:    ", event.name
    print "Status:   ", extra['status'].title()
    print "Organiser:", extra['organizer']
    print "Starts:   ", event.begin.to(tz).format(tzformat), "(local time)"
    print "Ends:     ", event.end.to(tz).format(tzformat)
    print "Location: ", event.location
    print "Description:"
    print (event.description or u'').encode('utf-8')
    print



This entry was originally posted at http://flexo.dreamwidth.org/3147.html.
Tags: code
Subscribe

  • Post a new comment

    Error

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 5 comments