Edgar Gonçalves | 26 Mar 22:19
Face
Picon

planner appointments exported to icalendar events


Hi,

after managing to keep all my personal information in Emacs (hooray to
planner, muse, gnus, and bbdb!), I came to the conclusion that the outside
world still uses other tools, and they often ask me for some portions of
information. One of such is my schedule. So I promised to give them an
iCalendar .ics file, and dug into Emacs to code some elisp to make it
work. First, I found icalendar.el, and then planner-ical. Both wouldn't work
for me:
1)  icalendar.el provides a simple API, directed to diary<->icalendar
    migration. It doesn't merge information :(
2) planner-ical.el is a work in progress, I suppose. It only converts tasks
   into VTODO, and only from one page at a time.

So I thought about what I needed:
o export all my future appointments into VEVENTs. This includes cyclic events,
  and tasks with time informations (both handled with planner-appt).
o import a full icalendar into planner. deal with VEVENT, VTODO, VJOURNAL, and
  create, respectively, schedule entries, tasks and notes. add the appointments
  retrieved from the VALARMS.

The code I made today fulfills, although not wonderfully, my first
requirement. I'm using it specially to communicate my availability to my
co-workers. So I added a regexp argument to filter each entry by its planner
category! The only thing I can't seem to manage is to get planner-appt to tell
me the category of a task-appointment. So right now I export all of the tasks!
Anyone knows how I can get this behavior?

Here's the code to export to a ics file:

;; iCalendar export of appointments:
(require 'icalendar)
;; TODO: Each VEVENT and VTODO will have a VALARM if it has an associated
;; appointment."

(defun planner-export-ical (category-regexp n ical-output-file)
  "Write ICAL-OUTPUT-FILE with iCalendar format. CATEGORY_REGEXP will
limit category marked entries, enabling filtering of the exported
data (up until N days from now).
The iCalendar exported data are all the planner task appointments and
all the cyclic diary entries (in `planner-cyclic-diary-file').
They are exported as VEVENTs."
  (interactive)
  ;; Get the appointments until end-date:
  (let ((appts (planner-appt-forthcoming-get-appts n (planner-today))))

    ;; And now add the cyclic appointments from the appropriate file:
    (with-temp-buffer
      (let ((cyclic-appts nil))
	(with-temp-buffer
	  (find-file planner-cyclic-diary-file)
	  (dolist (line (split-string (buffer-string) "[\n\r]"))
	    (unless (or (string= line "")
			(string-match "^!" line))
	      (push line cyclic-appts)))
	  (kill-buffer (current-buffer)))
	(dolist (appt-desc cyclic-appts)
	  (let ((appt-category (when (string-match "\\(.*\\)[ \t]*(\\(.*\\))$" appt-desc)
				 (match-string 2 appt-desc)))
		(appt-text (match-string 1 appt-desc)))
	    ;; Now filter by categories regexp:
	    (when (string-match category-regexp appt-category)
	      (when (string-match "\\(.*\\) @\\([0-2]?[0-9]:[0-5]?[0-9]\\)[ \t]*[-|][
\t]*\\([0-2]?[0-9]:[0-5]?[0-9]\\)[ \t]*|?[ \t]*\\(.*\\)$" appt-text)
		(let ((day        (match-string 1 appt-text))
		      (start-time (match-string 2 appt-text))
		      (end-time   (match-string 3 appt-text))
		      (text       (match-string 4 appt-text)))
		  (setq appt-text (format "%s %s-%s %s\n" day start-time end-time text))
		  (message "Adding cyclic appt to temp diary:::: %s" appt-text)
		  (insert appt-text)))))))	
      (dolist (appt appts)
	(let* ((appt-date (first appt))
	       (appt-desc (second appt))
	       (appt-category (when (string-match "\\(.*\\)(\\(.*\\))$" appt-desc)
				(match-string 2 appt-desc)))
	       (appt-text (when (string-match "\\(.*\\)\\((.*)\\)?$" appt-desc)
				(match-string 1 appt-desc))))
	  ;; Filter task appointments only:
	  (when (and appt-text
		     (string-match "\@?\\(.*\\)[ 	]*|?[ 	]#[	 ]*[-|]?\\(.*\\)$" appt-text))
	    ;; Now filter by categories regexp:
	    (if (or (null appt-category)
		    (string-match category-regexp appt-category))

		;; Extract time and text strings:
		(let ((appt-time (match-string 1 appt-text))
		      (appt-text (match-string 2 appt-text)))
		  
		  (setq appt-time (replace-in-string appt-time "|" ""))
		  (setq appt-time (replace-in-string appt-time " [ ]*" " "))
		  (setq appt-time (replace-in-string appt-time " $" ""))
		  (setq appt-time (replace-in-string appt-time " " "-"))
		  (setq appt-date (apply #'format "%s/%s/%s" (planner-filename-to-calendar-date appt-date)))
		  (message "Adding task to temp diary:::: %s %s %s" appt-date appt-time appt-text)
		  (insert (format "%s %s %s\n" appt-date appt-time appt-text)))
		(message "NOT adding task to temp diary:::: %s" appt-desc)))))
      (icalendar-export-region (point-min)
			       (point-max)
			       ical-output-file))))

;; icalendar fix (this function wouldn't work properly under my Windows + GNU
;; Emacs configuration, so I just removed a silly comparison:
(defalias 'icalendar--rris 'replace-regexp-in-string)

;; Usage example (export ical for the next 31 days):
;;  (planner-export-ical "." 31 "~/exported-planner-appts.ics")

--

-- 
Edgar Gonçalves
Software Engineering Group @ INESC-ID
IST/Technical University of Lisbon
Rua Alves Redol, 9, Room 635              
1000-029 Lisboa, Portugal                 
mailto:edgar[DOT]goncalves[AT]inesc[DASH]id[DOT]pt
http://www.esw.inesc-id.pt/~eemg

Gmane