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; copiousoutputAnd this in your
.muttrc:
auto_view text/calendarNow 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
print