/ical2org

Convert a calendar in ICal format (e.g., .ics) into org-mode structure

Primary LanguageGoGNU General Public License v3.0GPL-3.0

ical2org (used to be ical2org-go)

Convert a calendar in ICal format (e.g., .ics) into org-mode structure.

Usage: ical2org [-d=<duplicates>] [-o=output] [-a=append] [--inactive] [--active] [--deadline] [--scheduled] [--repeats] [-dupinput] [-count] [-after duration | date] [--label label] input files

The input files can be URLs ("http://...."), local files, or stdin. If the filename given is "-" then stdin is read.

The resulting org formatted events will either

  • replace the file specified with -o output,
  • be appended to the file specified with -a output
  • or be sent to stdout if -o and -a are not present.

Converted events have

  • a headline with the Summary from the event with a timestamp from event start time. It will be active by default. The option inactive can be used to make it an inactive timestamp.
  • an optional scheduling line: if --scheduled it will contain SCHEDULED; if --deadline it will contain DEADLINE.
  • a drawer ICALCONTENTS with potentially useful information extracted from the event
  • a subheading Description with the description field, and
  • a subheading Location with the Location information

Example output:

* DECISION MEETING: ITI Planning Monthly Call (11am CT, 12pm ET, 6pm CET) Host key: xxxxxx <2027-10-19 11:00>
    SCHEDULED: <2027-10-19 11:00-12:30>
  :ICALCONTENTS:
  :ORGUID: 1e23c027675290d3a25f3f8441bd91b2
  :ORIGINAL-UID: 040000008200E00074C5B7101A82E00800000000C0DE4F324F47D301000000000000000010000000ED14C4A947673341969029C8BE8EDA02
  :DTSTART: 2027-10-19 11:00
  :DTEND: 2027-10-19 12:30
  :DTSTAMP: 2017-10-17 19:04
  :ORGANIZER: <nil>
  :TZIDS: TZID="Central Standard Time":
  :END:
** Description

  Jeremiah Myers invites you to an online meeting using WebEx.
  
  Meeting Number: ccc ccc ccc
  Meeting Password: xxxxxxxxx
  
  -------------------------------------------------------
  To join this meeting
  -------------------------------------------------------
  1. Go to https://himss.webex.com/himss/j.php?MTID=m3d541
  ...

Other options:

  • label, provides a convert label that can be specified on the command line and which will be placed in ICALCONTENTS. This can be useful for debugging and tracing.
  • Repeats, see below
  • counts, sends to stdout a count of events converted. Should only be used when -o or -a are specified. This is useful when running fetch and convert automatically at intervals.
  • dupinput, see below
  • after, see below

Installation

To install this package make sure that you have the go toolchain installed https://golang.org/doc/code.html. Then use the commands

go get github.com/rjhorniii/ical2org
go install github.com/rjhorniii/ical2org

to download, build, and install the ical2org. You can use which ical2org to find the location of the binary on your $PATH.

To run the tests you can go to the project root directory and run

go test

Which will produce output like:

PASS
ok  	github.com/rjhorniii/ical2org	0.019s

Dependencies

ical2org depends upon the forked library in https://github.com/rjhorniii/ics-golang. When the fork/pull request are processed this will be changed.

Duplicates Management

If the duplicates option is present and provides a file, that file contains events that are already in org format. Duplicates of these events will not be generated as output. The duplicates file is scanned for ORGUIDs inside an ICALCONTENTS drawer. When the input files are processed, their event ORGUIDs are compared with the ORGUIDs from the duplicates file. Input events with matching ORGUIDs are not output.

This matching rule permits you to decide whether modified events are treated as duplicates or not. If you remove the ORGUID from the drawer, a modified event will be treated as different, and a new org headline generated when that Ical event is processed again. If the ORGUID matches, no matter what other changes are made, a new org headline will not be generated.

This means that by you can change task status to DONE, etc., and the modified event will still be considered a duplicate. You need not ensure that all Ical sources are also updated when you change the org file. This allows later conversion of old email attachments and calendars that contain historical events. New events will not be created in the org file for those duplicates.

The duplicates file can also be an orgmode file that is manually maintained. In the manually maintained org drawer you list the ORGUIDs that should be considered duplicate:

* Dummy event headline
  :ICALCONTENTS:
  :ORGUID: skdivndlkjf123
  :ORGUID: skdivndlkjf42354
  :ORGUID: skdivndlkjf987
  :END:

The duplicates processing permits a construction like:

ical2org -d=events.org -a=events.org https://calendar.google.com/more-stuff

to be run regularly, even hourly, without filling the events.org file with duplicates. The internal logic waits for duplicates file processing to complete before it begins event generation, so it is safe to have the output file and duplicates file be the same file.

The duplicates option also eliminates duplicates that may occur in the inputs, so that if the same event is in multiple sources only one event will be generated. The dupinput flag can be used to eliminate duplicates in the input files in situations where a duplicates file is not used.

Repeating events

There are good reasons to convert a repeating event into a single org headline, and good reasons to replicate the repeating event as multiple separate headlines. The flag repeats is used to control this. If missing or present with repeats=true, multiple org headlines will be generated. If repeats=false, only one headline will be generated. In either case, the ICALCONTENTS will contain the repeat rule, e.g., :RRULE: FREQ=WEEKLY;UNTIL=20180325T035959Z;BYDAY=SA.

The ORGUID will be the same for all of the headlines. This enables later efforts to find them if the repeating events need to be modified or rescheduled.

Time Zones

Org-mode does not use time zone tagged timestamps. There are a variety of good reasons for this. As soon as you deal with significant travel and teleconferences that originate in various parts of the world you hit complex edge conditions. The person involved can establish the right thing to do fairly easily. I've found no software calendar that handles this properly. For example, if I plan to be in New York on Monday and Tuesday, Chicago on Wednesday, and Berlin on Thursday and Friday, what timezone should be attached to which events? I personally use the rule: local time there and then. So I want Monday and Tuesday to be America/NewYork, Wednesday to be America/Chicago, and Thursday/Friday to be Europe/Berlin. Note that I did not use UTC offsets. I want the times to be then local time. I don't want to worry about whether at that time and location it is summer time or not. I find org agendas are most useful this way, even though different days and events are different time zones.

This program converts everything assuming that the event creator local time zone is the one that matches my rule of "then/there time". This is most often, but not always, correct. It is especially a problem for teleconferences across time zones. In the property TZIDS are the creator specified time zone(s) for the event. This allows a human to know what was sent. I then assume that between the description, the zones, and the times, a person can decide whether and how to adjust the timestamps in the org file.

After option

The after option specifies that only events after a particular time should be converted. The variations in calendar software that generate Ical events, human error, time zones, and summer time can make this a bit unpredictable. The effort to use "then/there local time" also makes it a bit unpredictable. For example, person in New Zealand may occasionally receive future events that originate in the Americas that appear to be scheduled in the past. It is a good idea to keep this cutoff at least a few days in the past.

There are two forms permitted:

-after -96h specifies a duration before now, the time the program is run. This must be specified in hours.

-after 2017-12-31 specifies the midnight beginning the first day that will generate events. This must be in yyyy-mm-dd notation. If the intention had been to only get events in 2018, the option should have been -after 2018-01-01.

Systemd example

The following files specify using ical2org to fetch a calendar from Google and update events-g.org at specified times.

Google-fetch.sh

#!/bin/bash
cd ~/org-directory
export https_proxy=https://192.168.1.1:8000
/home/rjhorniii/bin/ical2org -d=events-g.org -a=events-g.org https://calendar.google.com/google-stuff

google-fetch.service

[Unit]
Description=Fetch events from my google calendar into events-g.org

[Service]
Type=oneshot
ExecStart=/home/rjhorniii/org-directory/google-fetch.sh

google-fetch.timer, this example shows an irregular polling interval

[Unit]
Description=Fetch events timer

[Timer]
OnCalendar=*-*-* 6,8,10,12,13,14,16,18,23:10
Unit=google-fetch.service
Persistent=true

[Install]
WantedBy=timers.target

Put the service and timer in ~/.config/systemd/user. To run it immediately and then at the usual schedule use the commands:

systemctl --user daemon-reload
systemctl --user start google-fetch.timer
systemctl --user enable google-fetch.timer

Mu4e integration

The following additions to your .emacs simplifies extracting schedule from email. In the email view, the keystrokes A,s will start processing and ask for the attachment number to process. The result will be a buffer indicating the number of events captured.

;; define pipes to parse appointments from mu4e

(defun process-ical-appointments (msg attachnum)
  "schedule appointments onto /home/rjhorniii/org/events-g.org"
  (mu4e-view-pipe-attachment msg attachnum
  	"/home/rjhorniii/bin/ical2org -count -d=/home/rjhorniii/org/events-g.org -a=/home/rjhorniii/org/events-g.org -"))
;; define 's' as the shortcut
(add-to-list 'mu4e-view-attachment-actions '("schedule appointment" . process-ical-appointments) t)

(defun view-ical-appointments (msg attachnum)
  "convert ical attachment and show in buffer"
  (mu4e-view-pipe-attachment msg attachnum "/home/hornrj/bin/ical2org -"))
;; define 'v' as the shortcut
(add-to-list 'mu4e-view-attachment-actions '("view appointment" . view-ical-appointments) t)

The -count indicates that the generated event count be sent to stdout. The file events-g.org will be updated. The use of - indicates that input will be on stdin. Mu4e deals with extracting the attachment and sending it to the indicated command, and taking the output and showing it as an emacs buffer.

The keystrokes A, v will place the resulting org-mode event in a buffer (not in org mode) for the user to examine, modify, save, etc.