help-gnu-emacs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Emacs and google calendar


From: Sven Bretfeld
Subject: Re: Emacs and google calendar
Date: 19 Apr 2011 10:37:56 +0200
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/23.1.50 (gnu/linux)

Hi Daniel

A sync in the full sense is not possible at the moment. But you can come
quite close to it. The only thing I don't know a solution for, is
automatic deletion of GoogleCalendar entries when you delete it in
orgmode. 

Anyway, you have the possibility of using the Android app ConnectBot to
login to a running computer which runs emacs --daemon. If you log in by
a rsa-key and configure ConnectBot to immediately send the command
'emacsclient -t', a full Emacs is available on the phone within three
seconds. If you have a flatrate on your phone this is a convenient
method. 

If this is not what you want and you want to use GoogleCalendar you have
to do four things:

1. Create an ics file of your existing dates in Orgmode. Create a
   special calendar for it in GoogleCalendar (not the default one), name
   it org (or whatever) and import the ics file to that calendar.

2. Automatically upload new dates in Orgmode to GoogleCalendar:

This can be done using the GoogleCL interface. Here is the code by Eric
Fraga:

--8<---------------cut here---------------start------------->8---
(setq org-agenda-diary-file "~/Dropbox/myconf/diary.org") #change this

(defadvice org-agenda-add-entry-to-org-agenda-diary-file 
(after add-to-google-calendar)
"Add a new Google calendar entry that mirrors the diary entry just created by 
org-mode."
(let ((type (ad-get-arg 0))
(text (ad-get-arg 1))
(d1 (ad-get-arg 2))
(year1 (nth 2 d1))
(month1 (car d1))
(day1 (nth 1 d1))
(d2 (ad-get-arg 3))
entry dates)
(if (or (not (eq type 'block)) (not d2))
(setq dates (format "%d-%02d-%02d" year1 month1 day1))
(let ((year2 (nth 2 d2)) (month2 (car d2)) (day2 (nth 1 d2)) (repeats (- 
(calendar-absolute-from-gregorian d1)
(calendar-absolute-from-gregorian d2))))
(if (> repeats 0)
(setq dates (format "%d-%02d-%02d every day for %d days" year1 
month1 day1 (abs repeats)))
(setq dates (format "%d-%02d-%02d every day for %d days" year1 month1 
day1 (abs repeats))))
))
(setq entry (format "/usr/bin/google calendar add --cal org \'%s %s\'" 
text dates))
;;(message entry)
(if (not (string= "offline" mail-host-address))
(shell-command entry)
(let ((offline "~/tmp/org2google-offline-entries"))
(find-file offline)
(goto-char (point-max))
(insert (concat entry "\n"))
(save-buffer)
(kill-buffer (current-buffer))
(message "Plain text written to %s" offline)))))
(ad-activate 'org-agenda-add-entry-to-org-agenda-diary-file)
--8<---------------cut here---------------end--------------->8---

You have to install GoogleCL on your PC. If you use Ubuntu Natty you can
just apt-get it. For another Os you have to install it manually. It's
available from the Google website. Under Linux you also have to install
python-gdata. 

Now, if you add an entry by hitting "i d" in the agenda-view, a script
starts and adds your entry in the "org" calendar of Google. You should
test the command manually first ($ google calendar add --cal org 'Dinner
today at 5:00'), because Google will send a message to allow your
computer to access your calendars. You also have to setup a config file
with the credentials. This is explained on the GoogleCL site.

3. Import new Google entries to orgmode (the other way round)

This can be done with a bash script that calls an awk-script by Eric
Fraga, which you run by a cronjob every 30 minutes or so. Here are the
scripts:

importgoogle.sh:

--8<---------------cut here---------------start------------->8---
#!/bin/bash

ICS=/home/sven/bin/google2org/basic.ics #you have to change the path
ORG=/home/sven/Dropbox/myconf/googlecalendar.org #you have to change the path
AWK=/home/sven/bin/google2org/ical2org.awk #you have to change the path


cd /home/sven/bin/google2org/ #change this
# get the Google calendar
wget http://www.google.com/calendar/ical/.../basic.ics 
#fill the ... with the ical address of your main calendar which you can
#find in the settings of GoogleCalendar

# convert the ical entries to org format, adjusting for the
# time zone information

# this next command yields hours from UTC, + or -, times 100
# Note: this does not cater for those people living in time zones
# that are not aligned with discrete hours (e.g. Newfoundland)...
timezone=$(date +%z | sed 's/^\([+-]\)0/\1/')

# convert this to seconds for use in the awk script
seconds=$(($timezone*36))

# and now process the ics file with appropriate time zone
awk -f $AWK < $ICS > $ORG

rm $ICS
--8<---------------cut here---------------end--------------->8---

ical2org.awk:

--8<---------------cut here---------------start------------->8---
# awk script for converting an iCal formatted file to a sequence of org-mode 
headings.
# this may not work in general but seems to work for day and timed events from 
Google's
# calendar, which is really all I need right now...
#
# usage:
#   awk -f THISFILE < icalinputfile.ics > orgmodeentries.org
#
# Note: change org meta information generated below for author and
# email entries!
#
# Known bugs:
# - not so much a bug as a possible assumption: date entries with no time
#   specified are assumed to be independent of the time zone.
#
# Eric S Fraga
# 20100629 - initial version
# 20100708 - added end times to timed events
#          - adjust times according to time zone information
#          - fixed incorrect transfer for entries with ":" embedded within the 
text
#          - added support for multi-line summary entries (which become 
headlines)
# 20100709 - incorporated time zone identification
#          - fixed processing of continuation lines as Google seems to
#            have changed, in the last day, the number of spaces at
#            the start of the line for each continuation...
#          - remove backslashes used to protect commas in iCal text entries
#
# Last change: 2010.07.21 17:03:01
#----------------------------------------------------------------------------------

# a function to take the iCal formatted date+time, convert it into an
# internal form (seconds since time 0), and adjust according to the
# local time zone (specified by +-seconds calculated in the BEGIN
# section)

function datetimestamp(input)
{
    # convert the iCal Date+Time entry to a format that mktime can understand
    datespec = 
gensub("([0-9][0-9][0-9][0-9])([0-9][0-9])([0-9][0-9])T([0-9][0-9])([0-9][0-9])([0-9][0-9]).*[\r]*",
 "\\1 \\2 \\3 \\4 \\5 \\6", "g", input);
    # print "date spec : " datespec; convert this date+time into
    # seconds from the beginning of time and include adjustment for
    # time zone, as determined in the BEGIN section below.  For time
    # zone adjustment, I have not tested edge effects, specifically
    # what happens when UTC time is a different day to local time and
    # especially when an event with a duration crosses midnight in UTC
    # time.  It should work but...
    timestamp = mktime(datespec) + seconds;
    # print "adjusted    : " timestamp
    # print "Time stamp  : " strftime("%Y-%m-%d %H:%M", timestamp);
    return timestamp;
}

BEGIN {
    # use a colon to separate the type of data line from the actual contents
    FS = ":";
    
    # determine the number of seconds to use for adjusting for time
    # zone difference from UTC.  This is used in the function
    # datetimestamp above.  The time zone information returned by
    # strftime() is in hours * 100 so we multiply by 36 to get
    # seconds.  This does not work for time zones that are not an
    # integral multiple of hours (e.g. Newfoundland)
    seconds = gensub("([+-])0", "\\1", "", strftime("%z")) * 36;
    
    date = "";
    entry = ""
    first = 1;                  # true until an event has been found
    headline = ""
    icalentry = ""  # the full entry for inspection
    id = ""
    indescription = 0;
    time2given = 0;
    
    print "#+AUTHOR:    Sven Bretfeld"
    print "#+EMAIL:     sven.bretfeld@rub.de"
    print "#+DESCRIPTION: converted using the ical2org awk script"
    print "#+CATEGORY: google"
    print " "
}

# continuation lines (at least from Google) start with two spaces
# if the continuation is after a description or a summary, append the entry
# to the respective variable

/^[ ]+/ { 
    if (indescription) {
        entry = entry gensub("\r", "", "g", gensub("^[ ]+", "", "", $0));
    } else if (insummary) {
        summary = summary gensub("\r", "", "g", gensub("^[ ]+", "", "", $0))
    }
    icalentry = icalentry "\n" $0
}

/^BEGIN:VEVENT/ {
    # start of an event.  if this is the first, output the preamble from the 
iCal file
    if (first) {
        print "* COMMENT original iCal preamble"
        print gensub("\r", "", "g", icalentry)
        icalentry = ""
    }
    first = false;
}
# any line that starts at the left with a non-space character is a new data 
field

/^[A-Z]/ {
    # we ignore DTSTAMP lines as they change every time you download
    # the iCal format file which leads to a change in the converted
    # org file as I output the original input.  This change, which is
    # really content free, makes a revision control system update the
    # repository and confuses.
    if (! index("DTSTAMP", $1)) icalentry = icalentry "\n" $0
    # this line terminates the collection of description and summary entries
    indescription = 0;
    insummary = 0;
}

# this type of entry represents a day entry, not timed, with date stamp YYYYMMDD

/^DTSTART;VALUE=DATE/ {
    date = gensub("([0-9][0-9][0-9][0-9])([0-9][0-9])([0-9][0-9]).*[\r]", 
"\\1-\\2-\\3", "g", $2)
    # print date
}

# this represents a timed entry with date and time stamp YYYYMMDDTHHMMSS
# we ignore the seconds

/^DTSTART:/ {
    # print $0
    date = strftime("%Y-%m-%d %a %H:%M", datetimestamp($2));
    # print date;
}

# and the same for the end date; here we extract only the time and append this 
to the 
# date+time found by the DTSTART entry.  We assume that entry was there, of 
course.
# should probably add some error checking here!  In time...

/^DTEND:/ {
    # print $0
    time2 = strftime("%H:%M", datetimestamp($2));
    date = date "-" time2;
}

# The description will the contents of the entry in org-mode.
# this line may be continued.

/^DESCRIPTION/ { 
    $1 = "";
    entry = entry "\n" gensub("\r", "", "g", $0);
    indescription = 1;
}

# the summary will be the org heading

/^SUMMARY/ { 
    $1 = "";
    summary = gensub("\r", "", "g", $0);
    insummary = 1;
}

# the unique ID will be stored as a property of the entry

/^UID/ { 
    $1 = "";
    id = gensub("\r", "", "g", $0);
}

# when we reach the end of the event line, we output everything we
# have collected so far, creating a top level org headline with the
# date/time stamp, unique ID property and the contents, if any

/^END:VEVENT/ {
    # translate \n sequences to actual newlines and unprotect commas (,)
    print "* " gensub("\\\\,", ",", "g", gensub("\\\\n", " ", "g", summary))
    print "  :PROPERTIES:"
    print "  :ID:       " id
    print "  :END:"
    print "  <" date ">"
    # for the entry, convert all embedded "\n" strings to actual newlines
    print ""
    # translate \n sequences to actual newlines and unprotect commas (,)
    print gensub("\\\\,", ",", "g", gensub("\\\\n", "\n", "g", entry));
    print "** COMMENT original iCal entry"
    print gensub("\r", "", "g", icalentry)
    summary = ""
    date = ""
    entry = ""
    icalentry = ""
    indescription = 0
    insummary = 0
}

# Local Variables:
# time-stamp-line-limit: 1000
# time-stamp-format: "%04y.%02m.%02d %02H:%02M:%02S"
# time-stamp-active: t
# time-stamp-start: "Last change:[ \t]+"
# time-stamp-end: "$"
# End:
--8<---------------cut here---------------end--------------->8---

Please note the comments. You have to change some lines according to
your needs. Set up a cronjob for importgoogle.sh. After that all dates
created in your Google main calendar are imported in orgmode format to
the file googlecalendar.org. You have to make this file part of your
agenda-files.

Please note that you need two Google Calendars (on the same account, of
course). One, org, will contain all your data created with Emacs. The
other one, the one named like your Googlemail address and which I have
called 'Google main calendar' above, will contain all data added to
Google directly en route with your phone or from a web-interface. Both
are necessary. Once a month you can clear your main calendar by refiling
all dates from diary.org and googlecalendar.org to your main org file.
Then delete everything in your Google main calendar, delete the Google
calendar named org, recreate it and make a fresh import of a new ics
file.

4. Set up MobileOrg on your Android for TODO lists.

The whole four-step process looks complicated, but is not really hard to
do. Anyway, it is stable and trustworthy for me. I have all conveniences
of GoogleCalendar, including the use of Android widgets, without
abandoning orgmode. You can also consider using Tasker to automatically
mute the phone in meetings. Just add a keyword like [mute] to the
respective calendar entries and setup Tasker to mute the phone whenever
GoogleCalendar shows a date containing this keyword.

I hope that helps.

Greetings,

Sven

Daniel Dalton <d.dalton@iinet.net.au> writes:

> Hi, 
>
> Is there away to have my Google calendar sync with my emacs calendar?
> I'm not sure what emacs calendar to use, but I do currently use org-mode
> to schedule todo lists. If I could sync these with google calendar as
> well as creating events for specific dates and times as well as
> recurring events from within emacs that would be great. Then being able
> to update the emacs calendar with my gmail calendar regularly would be
> great so that appointments I schedule on my Android appear in emacs.  
>
> Is this possible? It'd be really good if emacs could also maintain a
> local copy of the calendar for times when I'm offline. 
>
> Thanks for any help, 
>
> Dan



reply via email to

[Prev in Thread] Current Thread [Next in Thread]