Invites to work meetings and such may be distributed via mail, often by means of a multi-part message enclosing a text/calendar file.
This type of file looks roughly like this:
BEGIN:VCALENDAR
METHOD:REQUEST
PRODID:Microsoft Exchange Server 2010
VERSION:2.0
BEGIN:VTIMEZONE
TZID:W. Europe Standard Time
BEGIN:STANDARD
DTSTART:16010101T030000
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:16010101T020000
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
ORGANIZER;CN=Sir Reginald:MAILTO:sir.reginald@dukes.org
ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=Freddy:MAILTO:mercury@queen.io
ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=Eddie:MAILTO:eddie@ironmaiden.com
DESCRIPTION;LANGUAGE=en-US:yada yada
SUMMARY;LANGUAGE=en-US:Got work to do
DTSTART;TZID=W. Europe Standard Time:20191115T143000
DTEND;TZID=W. Europe Standard Time:20191115T153000
UID:djfklsajfdklasjfklajsdafklsjkslddjfkasljf07C9193DA84D501000000000000000
CLASS:PUBLIC
PRIORITY:5
DTSTAMP:20191017T105343Z
TRANSP:OPAQUE
STATUS:CONFIRMED
SEQUENCE:0
LOCATION;LANGUAGE=en-US:Studios
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:REMINDER
TRIGGER;RELATED=START:-PT15M
END:VALARM
END:VEVENT
END:VCALENDAR
ICalendar is a common format, interpreted transparently by mainstream mail applications that embed a calendar management extension, while it looks close to gibberish to a human eye.
A convenient programmatic way to parse it could be to install a library
such as icalendar
(Python), and use it as a basis for a simple script like this:
#!/usr/bin/env python3
import icalendar
import sys
def print_cal(input_file, output_file):
details = icalendar.Calendar.from_ical(input_file.read())
for component in details.walk():
if component.name != "VEVENT":
continue
summary = component.get("summary")
start = component.get("dtstart").dt
end = component.get("dtend").dt
uid = component.get("uid")
location = component.get("location")
attendees = component.get("attendee")
print(
f"{start.month}/{start.day}\t{summary}",
f"\tfrom {start.hour:02d}:{start.minute:02d}",
f"\tto {end.hour:02d}:{end.minute:02d}",
f"\tlocation {location}",
sep="\n", file=output_file
)
print(
*(f"\tattendees {a}" for a in (attendees or [])),
sep="\n", file=output_file
)
print(
f"\t/* {uid} */",
sep="\n", file=output_file
)
if len(sys.argv) > 1:
with open(sys.argv[1], "rt", encoding="UTF-8") as f:
print_cal(f, sys.stdout)
else:
print_cal(sys.stdin, sys.stdout)
Feeding the ICalendar file to this script results in a simple and clear representation:
11/15 Got work to do
from 14:30
to 15:30
location Studios
attendees MAILTO:mercury@queen.io
attendees MAILTO:eddie@ironmaiden.com
/* djfklsajfdklasjfklajsdafklsjkslddjfkasljf07C9193DA84D501000000000000000 */
Integration with mutt
Mutt, the mail user agent that sucks less, doesn't have a calendar feature. And why should it anyway? In true Unix fashion it does one thing and it does it well: it handles email messages.
On the other hand Mutt honours the ~/.mailcap
file, which determines what
specialized viewer to use depending on the file type. Under the default key
bindings, attachment can be listed with the v
key, and the viewer can be
invoked by pressing the m
key while the desired attachment is selected.
The Mailcap feature is documented thoroughly, so I'll just show a short
excerpt from my own ~/.mailcap
file:
$ more ~/.mailcap
image/jpeg; ristretto %s
application/pdf; qpdfview %s
application/rtf; libreoffice %s
--More--
The following is a very useful line to deal with those obnoxious HTML emails (many web pages are devoted to this subject):
text/html; lynx -dump -force_html -image_links -nopause -cfg ~/.config/lynx.cfg %s; copiousoutput;
--More--
And assuming that you saved the previous script as
/usr/local/bin/parse-cal
(or somewhere in your $PATH
), here's how you
get mutt to parse automatically the ICalendar file by means of it:
text/calendar; parse-cal %s; copiousoutput;
application/ics; parse-cal %s; copiousoutput;
NOTE: Don't forget the copiousoutput
directive, or Mutt won't display the output
correctly!
Pipe the file and store the result
Besides being obviously easier to understand at a glance, the output of
parse-cal
is in a format that can be understood by a classic Unix command
named calendar(1).
To briefly quote its man page:
The calendar utility checks the current directory or the directory specified by the CALENDAR_DIR environment variable for a file named calendar and displays lines that begin with either today's date or tomorrow's.
Mutt can pipe the message or attachment to a shell commands: typing |
results
in a Pipe to:
prompt that can be parametrised with a shell command.
Many Unix commands read the input from STDIN if no file name is provided as
argument. Our parse-cal
script is no exception, so the attachment can be
piped to it, and the output can be appended in turn to the
~/.calendar/calendar
file:
| parse-cal >> ~/.calendar/calendar
(In reality I actually prefer to just visualise the invitation, and manually annotate it into my calendar: I can often be more concise while mentally acknowledging the scheduled event).
Daily verification of future events
I instructed cron(1)
to check my calendar every day at 9:00.
$ crontab -l | grep calendar
0 9 * * * /usr/bin/calendar -A2
If calendar(1)
emits some output, cron(1)
will turn it into my local mail,
and I can get notified in many ways (e.g. check the bash(1)
manual for the
MAILCHECK
environment variable).
And in case you are wondering: yes, I still manage to forget about meeting, sometimes.