(pc-select-selection-keys-only, pc-selection-mode): Fix spellings in docstrings.
[bpt/emacs.git] / lisp / calendar / icalendar.el
index dc3bf01..d15a4b7 100644 (file)
@@ -1,6 +1,6 @@
 ;;; icalendar.el --- iCalendar implementation -*-coding: utf-8 -*-
 
-;; Copyright (C) 2002, 2003, 2004  Free Software Foundation, Inc.
+;; Copyright (C) 2002, 2003, 2004, 2005  Free Software Foundation, Inc.
 
 ;; Author:         Ulf Jasper <ulf.jasper@web.de>
 ;; Created:        August 2002
 
 ;; This package is documented in the Emacs Manual.
 
+;;   Please note:
+;; - Diary entries which have a start time but no end time are assumed to
+;;   last for one hour when they are exported.
+;; - Weekly diary entries are assumed to occur the first time in the first
+;;   week of the year 2000 when they are exported.
+;; - Yearly diary entries are assumed to occur the first time in the year
+;;   1900 when they are exported.
 
 ;;; History:
 
 ;;    + the parser is too soft
 ;;    + error log is incomplete
 ;;    + nice to have: #include "webcal://foo.com/some-calendar.ics"
+;;    + timezones, currently all times are local!
 
 ;;  * Export from diary to ical
 ;;    + diary-date, diary-float, and self-made sexp entries are not
 ;;      understood
-;;    + timezones, currently all times are local!
 
 ;;  * Other things
 ;;    + clean up all those date/time parsing functions
@@ -90,7 +97,7 @@
 
 ;;; Code:
 
-(defconst icalendar-version 0.08
+(defconst icalendar-version 0.12
   "Version number of icalendar.el.")
 
 ;; ======================================================================
@@ -145,16 +152,8 @@ replaced by the organizer."
   :type 'string
   :group 'icalendar)
 
-(defcustom icalendar-duration-correction
-  t
-  "Workaround for all-day events.
-If non-nil the length=duration of iCalendar appointments that
-have a length of exactly n days is decreased by one day.  This
-fixes problems with all-day events, which appear to be one day
-longer than they are."
-  :type 'boolean
-  :group 'icalendar)
-
+(defvar icalendar-debug nil
+  "Enable icalendar debug messages.")
 
 ;; ======================================================================
 ;; NO USER SERVICABLE PARTS BELOW THIS LINE
@@ -162,13 +161,10 @@ longer than they are."
 
 (defconst icalendar--weekday-array ["SU" "MO" "TU" "WE" "TH" "FR" "SA"])
 
-(defvar icalendar-debug nil ".")
-
 ;; ======================================================================
 ;; all the other libs we need
 ;; ======================================================================
 (require 'calendar)
-(require 'appt)
 
 ;; ======================================================================
 ;; misc
@@ -268,7 +264,7 @@ it finds"
 ;;  (car (cddr event)))
 
 (defun icalendar--get-event-property (event prop)
-  "For the given EVENT return the value of the property PROP."
+  "For the given EVENT return the value of the first occurence of PROP."
   (catch 'found
     (let ((props (car (cddr event))) pp)
       (while props
@@ -278,6 +274,27 @@ it finds"
         (setq props (cdr props))))
     nil))
 
+(defun icalendar--get-event-property-attributes (event prop)
+  "For the given EVENT return attributes of the first occurence of PROP."
+  (catch 'found
+    (let ((props (car (cddr event))) pp)
+      (while props
+        (setq pp (car props))
+        (if (eq (car pp) prop)
+            (throw 'found (cadr pp)))
+        (setq props (cdr props))))
+    nil))
+
+(defun icalendar--get-event-properties (event prop)
+  "For the given EVENT return a list of all values of the property PROP."
+  (let ((props (car (cddr event))) pp result)
+    (while props
+      (setq pp (car props))
+      (if (eq (car pp) prop)
+          (setq result (append (split-string (car (cddr pp)) ",") result)))
+      (setq props (cdr props)))
+    result))
+
 ;; (defun icalendar--set-event-property (event prop new-value)
 ;;   "For the given EVENT set the property PROP to the value NEW-VALUE."
 ;;   (catch 'found
@@ -319,7 +336,7 @@ children."
   (icalendar--get-children (car icalendar) 'VEVENT))
 
 (defun icalendar--split-value (value-string)
-  "Splits VALUE-STRING at ';='."
+  "Split VALUE-STRING at ';='."
   (let ((result '())
         param-name param-value)
     (when value-string
@@ -339,12 +356,16 @@ children."
                 (append result (list (list param-name param-value)))))))
     result))
 
-(defun icalendar--decode-isodatetime (isodatetimestring)
+(defun icalendar--decode-isodatetime (isodatetimestring &optional day-shift)
   "Return ISODATETIMESTRING in format like `decode-time'.
-Converts from ISO-8601 to Emacs representation.  If ISODATETIMESTRING
-specifies UTC time (trailing letter Z) the decoded time is given in
-the local time zone! FIXME: TZID-attributes are ignored....! FIXME:
-multiple comma-separated values should be allowed!"
+Converts from ISO-8601 to Emacs representation.  If
+ISODATETIMESTRING specifies UTC time (trailing letter Z) the
+decoded time is given in the local time zone!  If optional
+parameter DAY-SHIFT is non-nil the result is shifted by DAY-SHIFT
+days.
+
+FIXME: TZID-attributes are ignored....!
+FIXME: multiple comma-separated values should be allowed!"
   (icalendar--dmsg isodatetimestring)
   (if isodatetimestring
       ;; day/month/year must be present
@@ -366,6 +387,15 @@ multiple comma-separated values should be allowed!"
                    (char-equal ?Z (aref isodatetimestring 15)))
           ;; if not UTC add current-time-zone offset
           (setq second (+ (car (current-time-zone)) second)))
+        ;; shift if necessary
+        (if day-shift
+            (let ((mdy (calendar-gregorian-from-absolute
+                        (+ (calendar-absolute-from-gregorian
+                            (list month day year))
+                           day-shift))))
+              (setq month (nth 0 mdy))
+              (setq day   (nth 1 mdy))
+              (setq year  (nth 2 mdy))))
         ;; create the decoded date-time
         ;; FIXME!?!
         (condition-case nil
@@ -377,12 +407,17 @@ multiple comma-separated values should be allowed!"
     ;; isodatetimestring == nil
     nil))
 
-(defun icalendar--decode-isoduration (isodurationstring)
-  "Return ISODURATIONSTRING in format like `decode-time'.
+(defun icalendar--decode-isoduration (isodurationstring
+                                      &optional duration-correction)
+  "Convert ISODURATIONSTRING into format provided by `decode-time'.
 Converts from ISO-8601 to Emacs representation.  If ISODURATIONSTRING
 specifies UTC time (trailing letter Z) the decoded time is given in
-the local time zone! FIXME: TZID-attributes are ignored....! FIXME:
-multiple comma-separated values should be allowed!"
+the local time zone!
+
+Optional argument DURATION-CORRECTION shortens result by one day.
+
+FIXME: TZID-attributes are ignored....!
+FIXME: multiple comma-separated values should be allowed!"
   (if isodurationstring
       (save-match-data
         (string-match
@@ -406,7 +441,7 @@ multiple comma-separated values should be allowed!"
             (setq days (read (substring isodurationstring
                                         (match-beginning 3)
                                         (match-end 3))))
-            (when icalendar-duration-correction
+            (when duration-correction
               (setq days (1- days))))
            ((match-beginning 4)         ;days and time
             (if (match-beginning 5)
@@ -424,13 +459,11 @@ multiple comma-separated values should be allowed!"
             (if (match-beginning 11)
                 (setq seconds (read (substring isodurationstring
                                                (match-beginning 12)
-                                               (match-end 12)))))
-            )
+                                               (match-end 12))))))
            ((match-beginning 13)        ;weeks only
             (setq days (* 7 (read (substring isodurationstring
                                              (match-beginning 14)
-                                             (match-end 14))))))
-           )
+                                             (match-end 14)))))))
           (list seconds minutes hours days months years)))
     ;; isodatetimestring == nil
     nil))
@@ -452,27 +485,45 @@ valid (year > 1900 or something)."
                 ;;(or (nth 6 time1) (nth 6 time2)) ;; FIXME?
                 )))
 
-(defun icalendar--datetime-to-noneuropean-date (datetime)
+(defun icalendar--datetime-to-noneuropean-date (datetime &optional separator)
   "Convert the decoded DATETIME to non-european-style format.
-Non-European format: (month day year)."
+Optional argument SEPARATOR gives the separator between month,
+day, and year.  If nil a blank character is used as separator.
+Non-European format: \"month day year\"."
   (if datetime
-      (list (nth 4 datetime)            ;month
-            (nth 3 datetime)            ;day
-            (nth 5 datetime))           ;year
+      (format "%d%s%d%s%d" (nth 4 datetime) ;month
+              (or separator " ")
+              (nth 3 datetime)          ;day
+              (or separator " ")
+              (nth 5 datetime))         ;year
     ;; datetime == nil
     nil))
 
-(defun icalendar--datetime-to-european-date (datetime)
+(defun icalendar--datetime-to-european-date (datetime &optional separator)
   "Convert the decoded DATETIME to European format.
+Optional argument SEPARATOR gives the separator between month,
+day, and year.  If nil a blank character is used as separator.
 European format: (day month year).
 FIXME"
   (if datetime
-      (format "%d %d %d" (nth 3 datetime) ; day
+      (format "%d%s%d%s%d" (nth 3 datetime) ;day
+              (or separator " ")
               (nth 4 datetime)            ;month
+              (or separator " ")
               (nth 5 datetime))           ;year
     ;; datetime == nil
     nil))
 
+(defun icalendar--datetime-to-diary-date (datetime &optional separator)
+  "Convert the decoded DATETIME to diary format.
+Optional argument SEPARATOR gives the separator between month,
+day, and year.  If nil a blank character is used as separator.
+Call icalendar--datetime-to-(non)-european-date according to
+value of `european-calendar-style'."
+  (if european-calendar-style
+      (icalendar--datetime-to-european-date datetime separator)
+    (icalendar--datetime-to-noneuropean-date datetime separator)))
+
 (defun icalendar--datetime-to-colontime (datetime)
   "Extract the time part of a decoded DATETIME into 24-hour format.
 Note that this silently ignores seconds."
@@ -495,15 +546,16 @@ Note that this silently ignores seconds."
 
 (defun icalendar--get-weekday-number (abbrevweekday)
   "Return the number for the ABBREVWEEKDAY."
-  (catch 'found
-    (let ((num 0)
-          (aw (downcase abbrevweekday)))
-      (mapc (lambda (day)
-              (let ((d (downcase day)))
-                (if (string-equal d aw)
-                    (throw 'found num))
-                (setq num (1+ num))))
-            icalendar--weekday-array))
+  (if abbrevweekday
+      (catch 'found
+        (let ((num 0)
+              (aw (downcase abbrevweekday)))
+          (mapc (lambda (day)
+                  (let ((d (downcase day)))
+                    (if (string-equal d aw)
+                        (throw 'found num))
+                    (setq num (1+ num))))
+                icalendar--weekday-array)))
     ;; Error:
     -1))
 
@@ -605,9 +657,9 @@ would be \"pm\"."
         (format "T%04d00" starttimenum))
     nil))
 
-(defun icalendar--convert-string-for-export (s)
-  "Escape comma and other critical characters in string S."
-  (icalendar--rris "," "\\\\," s))
+(defun icalendar--convert-string-for-export (string)
+  "Escape comma and other critical characters in STRING."
+  (icalendar--rris "," "\\\\," string))
 
 (defun icalendar--convert-string-for-import (string)
   "Remove escape chars for comma, semicolon etc. from STRING."
@@ -621,7 +673,7 @@ would be \"pm\"."
 ;; Export -- convert emacs-diary to icalendar
 ;; ======================================================================
 
-;; User function
+;;;###autoload
 (defun icalendar-export-file (diary-filename ical-filename)
   "Export diary file to iCalendar format.
 All diary entries in the file DIARY-FILENAME are converted to iCalendar
@@ -635,15 +687,15 @@ Finto iCalendar file: ")
 (defalias 'icalendar-convert-diary-to-ical 'icalendar-export-file)
 (make-obsolete 'icalendar-convert-diary-to-ical 'icalendar-export-file)
 
-;; User function
+;;;###autoload
 (defun icalendar-export-region (min max ical-filename)
   "Export region in diary file to iCalendar format.
 All diary entries in the region from MIN to MAX in the current buffer are
 converted to iCalendar format.  The result is appended to the file
 ICAL-FILENAME.
-
-Returns non-nil if an error occurred.  In this case an error message is
-written to the buffer ` *icalendar-errors*'."
+This function attempts to return t if something goes wrong.  In this
+case an error string which describes all the errors and problems is
+written into the buffer `*icalendar-errors*'."
   (interactive "r
 FExport diary data into iCalendar file: ")
   (let ((result "")
@@ -657,13 +709,14 @@ FExport diary data into iCalendar file: ")
                            "?")))
     ;; prepare buffer with error messages
     (save-current-buffer
-      (set-buffer (get-buffer-create " *icalendar-errors*"))
+      (set-buffer (get-buffer-create "*icalendar-errors*"))
       (erase-buffer))
+
     ;; here we go
     (save-excursion
       (goto-char min)
       (while (re-search-forward
-              "^\\([^ \t\n].*\\)\\(\\(\n[ \t].*\\)*\\)" max t)
+              "^\\([^ \t\n].+\\)\\(\\(\n[ \t].*\\)*\\)" max t)
         (setq entry-main (match-string 1))
         (if (match-beginning 2)
             (setq entry-rest (match-string 2))
@@ -674,370 +727,578 @@ FExport diary data into iCalendar file: ")
                              (car (cddr (current-time)))))
         (condition-case error-val
             (progn
-              (cond
-               ;; anniversaries
-               ((string-match
-                 (concat nonmarker
-                         "%%(diary-anniversary \\([^)]+\\))\\s-*\\(.*\\)")
-                 entry-main)
-                (icalendar--dmsg "diary-anniversary %s" entry-main)
-                (let* ((datetime (substring entry-main (match-beginning 1)
-                                            (match-end 1)))
-                       (summary (icalendar--convert-string-for-export
-                                 (substring entry-main (match-beginning 2)
-                                            (match-end 2))))
-                       (startisostring (icalendar--datestring-to-isodate
-                                        datetime))
-                       (endisostring (icalendar--datestring-to-isodate
-                                      datetime 1)))
-                  (setq contents
-                        (concat "\nDTSTART;VALUE=DATE:" startisostring
-                                "\nDTEND;VALUE=DATE:" endisostring
-                                "\nSUMMARY:" summary
-                                "\nRRULE:FREQ=YEARLY;INTERVAL=1"
-                                ;; the following is redundant,
-                                ;; but korganizer seems to expect this... ;(
-                                ;; and evolution doesn't understand it... :(
-                                ;; so... who is wrong?!
-                                ";BYMONTH=" (substring startisostring 4 6)
-                                ";BYMONTHDAY=" (substring startisostring 6 8)
-                                )))
-                (unless (string= entry-rest "")
-                  (setq contents (concat contents "\nDESCRIPTION:"
-                                         (icalendar--convert-string-for-export
-                                          entry-rest)))))
-               ;; cyclic events
-               ;; %%(diary-cyclic )
-               ((string-match
-                 (concat nonmarker
-                         "%%(diary-cyclic \\([^ ]+\\) +"
-                         "\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\))\\s-*\\(.*\\)")
-                 entry-main)
-                (icalendar--dmsg "diary-cyclic %s" entry-main)
-                (let* ((frequency (substring entry-main (match-beginning 1)
-                                             (match-end 1)))
-                       (datetime (substring entry-main (match-beginning 2)
-                                            (match-end 2)))
-                       (summary (icalendar--convert-string-for-export
-                                 (substring entry-main (match-beginning 3)
-                                            (match-end 3))))
-                       (startisostring (icalendar--datestring-to-isodate
-                                        datetime))
-                       (endisostring (icalendar--datestring-to-isodate
-                                      datetime 1)))
-                  (setq contents
-                        (concat "\nDTSTART;VALUE=DATE:" startisostring
-                                "\nDTEND;VALUE=DATE:" endisostring
-                                "\nSUMMARY:" summary
-                                "\nRRULE:FREQ=DAILY;INTERVAL=" frequency
-                                ;; strange: korganizer does not expect
-                                ;; BYSOMETHING here...
-                                )))
-                (unless (string= entry-rest "")
-                  (setq contents (concat contents "\nDESCRIPTION:"
-                                         (icalendar--convert-string-for-export
-                                          entry-rest)))))
-               ;; diary-date -- FIXME
-               ((string-match
-                 (concat nonmarker
-                         "%%(diary-date \\([^)]+\\))\\s-*\\(.*\\)")
-                 entry-main)
-                (icalendar--dmsg "diary-date %s" entry-main)
-                (error "`diary-date' is not supported yet"))
-               ;; float events -- FIXME
-               ((string-match
-                 (concat nonmarker
-                         "%%(diary-float \\([^)]+\\))\\s-*\\(.*\\)")
-                 entry-main)
-                (icalendar--dmsg "diary-float %s" entry-main)
-                (error "`diary-float' is not supported yet"))
-               ;; block events
-               ((string-match
-                 (concat nonmarker
-                         "%%(diary-block \\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\) +"
-                         "\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\))\\s-*\\(.*\\)")
-                 entry-main)
-                (icalendar--dmsg "diary-block %s" entry-main)
-                (let* ((startstring (substring entry-main (match-beginning 1)
-                                               (match-end 1)))
-                       (endstring (substring entry-main (match-beginning 2)
-                                             (match-end 2)))
-                       (summary (icalendar--convert-string-for-export
-                                 (substring entry-main (match-beginning 3)
-                                            (match-end 3))))
-                       (startisostring (icalendar--datestring-to-isodate
-                                        startstring))
-                       (endisostring (icalendar--datestring-to-isodate
-                                      endstring 1)))
-                  (setq contents
-                        (concat "\nDTSTART;VALUE=DATE:" startisostring
-                                "\nDTEND;VALUE=DATE:" endisostring
-                                "\nSUMMARY:" summary
-                                ))
-                  (unless (string= entry-rest "")
-                    (setq contents (concat contents "\nDESCRIPTION:"
-                                           (icalendar--convert-string-for-export
-                                            entry-rest))))))
-               ;; other sexp diary entries -- FIXME
-               ((string-match
-                 (concat nonmarker
-                         "%%(\\([^)]+\\))\\s-*\\(.*\\)")
-                 entry-main)
-                (icalendar--dmsg "diary-sexp %s" entry-main)
-                (error "sexp-entries are not supported yet"))
-               ;; weekly by day
-               ;; Monday 8:30 Team meeting
-               ((and (string-match
-                      (concat nonmarker
-                              "\\([a-z]+\\)\\s-+"
-                              "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
-                              "\\(-0?"
-                              "\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
-                              "\\)?"
-                              "\\s-*\\(.*\\)$")
-                      entry-main)
-                     (icalendar--get-weekday-abbrev
-                      (substring entry-main (match-beginning 1) (match-end 1))))
-                (icalendar--dmsg "weekly %s" entry-main)
-                (let* ((day (icalendar--get-weekday-abbrev
-                             (substring entry-main (match-beginning 1)
-                                        (match-end 1))))
-                       (starttimestring (icalendar--diarytime-to-isotime
-                                         (if (match-beginning 3)
-                                             (substring entry-main
-                                                        (match-beginning 3)
-                                                        (match-end 3))
-                                           nil)
-                                         (if (match-beginning 4)
-                                             (substring entry-main
-                                                        (match-beginning 4)
-                                                        (match-end 4))
-                                           nil)))
-                       (endtimestring (icalendar--diarytime-to-isotime
-                                       (if (match-beginning 6)
-                                           (substring entry-main
-                                                      (match-beginning 6)
-                                                      (match-end 6))
-                                         nil)
-                                       (if (match-beginning 7)
-                                           (substring entry-main
-                                                      (match-beginning 7)
-                                                      (match-end 7))
-                                         nil)))
-                       (summary (icalendar--convert-string-for-export
-                                 (substring entry-main (match-beginning 8)
-                                            (match-end 8)))))
-                  (when starttimestring
-                    (unless endtimestring
-                      (let ((time (read (icalendar--rris "^T0?" ""
-                                                         starttimestring))))
-                        (setq endtimestring (format "T%06d" (+ 10000 time))))))
-                  (setq contents
-                        (concat "\nDTSTART;"
-                                (if starttimestring
-                                    "VALUE=DATE-TIME:"
-                                  "VALUE=DATE:")
-                                ;; find the correct week day,
-                                ;; 1st january 2000 was a saturday
-                                (format
-                                 "200001%02d"
-                                 (+ (icalendar--get-weekday-number day) 2))
-                                (or starttimestring "")
-                                "\nDTEND;"
-                                (if endtimestring
-                                    "VALUE=DATE-TIME:"
-                                  "VALUE=DATE:")
-                                (format
-                                 "200001%02d"
-                                 ;; end is non-inclusive!
-                                 (+ (icalendar--get-weekday-number day)
-                                    (if endtimestring 2 3)))
-                                (or endtimestring "")
-                                "\nSUMMARY:" summary
-                                "\nRRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=" day
-                                )))
-                (unless (string= entry-rest "")
-                  (setq contents (concat contents "\nDESCRIPTION:"
-                                         (icalendar--convert-string-for-export
-                                          entry-rest)))))
-               ;; yearly by day
-               ;; 1 May Tag der Arbeit
-               ((string-match
-                 (concat nonmarker
-                         (if european-calendar-style
-                             "0?\\([1-9]+[0-9]?\\)\\s-+\\([a-z]+\\)\\s-+"
-                           "\\([a-z]+\\)\\s-+0?\\([1-9]+[0-9]?\\)\\s-+")
-                         "\\*?\\s-*"
-                         "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
-                         "\\("
-                         "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
-                         "\\)?"
-                         "\\s-*\\([^0-9]+.*\\)$" ; must not match years
-                         )
-                 entry-main)
-                (icalendar--dmsg "yearly %s" entry-main)
-                (let* ((daypos (if european-calendar-style 1 2))
-                       (monpos (if european-calendar-style 2 1))
-                       (day (read (substring entry-main (match-beginning daypos)
-                                             (match-end daypos))))
-                       (month (icalendar--get-month-number
-                               (substring entry-main (match-beginning monpos)
-                                          (match-end monpos))))
-                       (starttimestring (icalendar--diarytime-to-isotime
-                                         (if (match-beginning 4)
-                                             (substring entry-main
-                                                        (match-beginning 4)
-                                                        (match-end 4))
-                                           nil)
-                                         (if (match-beginning 5)
-                                             (substring entry-main
-                                                        (match-beginning 5)
-                                                        (match-end 5))
-                                           nil)))
-                       (endtimestring (icalendar--diarytime-to-isotime
-                                       (if (match-beginning 7)
-                                           (substring entry-main
-                                                      (match-beginning 7)
-                                                      (match-end 7))
-                                         nil)
-                                       (if (match-beginning 8)
-                                           (substring entry-main
-                                                      (match-beginning 8)
-                                                      (match-end 8))
-                                         nil)))
-                       (summary (icalendar--convert-string-for-export
-                                 (substring entry-main (match-beginning 9)
-                                            (match-end 9)))))
-                  (when starttimestring
-                    (unless endtimestring
-                      (let ((time (read (icalendar--rris "^T0?" ""
-                                                         starttimestring))))
-                        (setq endtimestring (format "T%06d" (+ 10000 time))))))
-                  (setq contents
-                        (concat "\nDTSTART;"
-                                (if starttimestring "VALUE=DATE-TIME:"
-                                  "VALUE=DATE:")
-                                (format "1900%02d%02d" month day)
-                                (or starttimestring "")
-                                "\nDTEND;"
-                                (if endtimestring "VALUE=DATE-TIME:"
-                                  "VALUE=DATE:")
-                                ;; end is not included! shift by one day
-                                (icalendar--date-to-isodate
-                                 (list month day 1900) (if endtimestring 0 1))
-                                (or endtimestring "")
-                                "\nSUMMARY:"
-                                summary
-                                "\nRRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH="
-                                (format "%2d" month)
-                                ";BYMONTHDAY="
-                                (format "%2d" day)
-                                )))
-                (unless (string= entry-rest "")
-                  (setq contents (concat contents "\nDESCRIPTION:"
-                                         (icalendar--convert-string-for-export
-                                          entry-rest)))))
-               ;; "ordinary" events, start and end time given
-               ;; 1 Feb 2003 Hs Hochzeitsfeier, Dreieich
-               ((string-match
-                 (concat nonmarker
-                         "\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\)\\s-+"
-                         "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
-                         "\\("
-                         "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
-                         "\\)?"
-                         "\\s-*\\(.*\\)")
-                 entry-main)
-                (icalendar--dmsg "ordinary %s" entry-main)
-                (let* ((startdatestring (icalendar--datestring-to-isodate
-                                         (substring entry-main
-                                                    (match-beginning 1)
-                                                    (match-end 1))))
-                       (starttimestring (icalendar--diarytime-to-isotime
-                                         (if (match-beginning 3)
-                                             (substring entry-main
-                                                        (match-beginning 3)
-                                                        (match-end 3))
-                                           nil)
-                                         (if (match-beginning 4)
-                                             (substring entry-main
-                                                        (match-beginning 4)
-                                                        (match-end 4))
-                                           nil)))
-                       (endtimestring (icalendar--diarytime-to-isotime
-                                       (if (match-beginning 6)
-                                           (substring entry-main
-                                                      (match-beginning 6)
-                                                      (match-end 6))
-                                         nil)
-                                       (if (match-beginning 7)
-                                           (substring entry-main
-                                                      (match-beginning 7)
-                                                      (match-end 7))
-                                         nil)))
-                       (summary (icalendar--convert-string-for-export
-                                 (substring entry-main (match-beginning 8)
-                                            (match-end 8)))))
-                  (unless startdatestring
-                    (error "Could not parse date"))
-                  (when starttimestring
-                    (unless endtimestring
-                      (let ((time (read (icalendar--rris "^T0?" ""
-                                                         starttimestring))))
-                        (setq endtimestring (format "T%06d" (+ 10000 time))))))
-                  (setq contents (concat
-                                  "\nDTSTART;"
-                                  (if starttimestring "VALUE=DATE-TIME:"
-                                    "VALUE=DATE:")
-                                  startdatestring
-                                  (or starttimestring "")
-                                  "\nDTEND;"
-                                  (if endtimestring "VALUE=DATE-TIME:"
-                                    "VALUE=DATE:")
-                                  (icalendar--datestring-to-isodate
-                                   (substring entry-main
-                                              (match-beginning 1)
-                                              (match-end 1))
-                                   (if endtimestring 0 1))
-                                  (or endtimestring "")
-                                  "\nSUMMARY:"
-                                  summary))
-                  ;; could not parse the date
-                  (unless (string= entry-rest "")
-                    (setq contents (concat contents "\nDESCRIPTION:"
-                                           (icalendar--convert-string-for-export
-                                            entry-rest))))))
-               ;; everything else
-               (t
-                ;; Oops! what's that?
-                (error "Could not parse entry")))
+              (setq contents
+                    (or
+                     ;; anniversaries -- %%(diary-anniversary ...)
+                     (icalendar--convert-anniversary-to-ical nonmarker
+                                                             entry-main)
+                     ;; cyclic events -- %%(diary-cyclic ...)
+                     (icalendar--convert-cyclic-to-ical nonmarker entry-main)
+                     ;; diary-date -- %%(diary-date ...)
+                     (icalendar--convert-date-to-ical nonmarker entry-main)
+                     ;; float events -- %%(diary-float ...)
+                     (icalendar--convert-float-to-ical nonmarker entry-main)
+                     ;; block events -- %%(diary-block ...)
+                     (icalendar--convert-block-to-ical nonmarker entry-main)
+                     ;; other sexp diary entries
+                     (icalendar--convert-sexp-to-ical nonmarker entry-main)
+                     ;; weekly by day -- Monday 8:30 Team meeting
+                     (icalendar--convert-weekly-to-ical nonmarker entry-main)
+                     ;; yearly by day -- 1 May Tag der Arbeit
+                     (icalendar--convert-yearly-to-ical nonmarker entry-main)
+                     ;; "ordinary" events, start and end time given
+                     ;; 1 Feb 2003 blah
+                     (icalendar--convert-ordinary-to-ical nonmarker entry-main)
+                     ;; everything else
+                     ;; Oops! what's that?
+                     (error "Could not parse entry")))
+              (unless (string= entry-rest "")
+                (setq contents
+                      (concat contents "\nDESCRIPTION:"
+                              (icalendar--convert-string-for-export
+                               entry-rest))))
               (setq result (concat result header contents "\nEND:VEVENT")))
           ;; handle errors
           (error
            (setq found-error t)
            (save-current-buffer
-             (set-buffer (get-buffer-create " *icalendar-errors*"))
+             (set-buffer (get-buffer-create "*icalendar-errors*"))
              (insert (format "Error in line %d -- %s: `%s'\n"
                              (count-lines (point-min) (point))
                              (cadr error-val)
                              entry-main))))))
 
       ;; we're done, insert everything into the file
-      (let ((coding-system-for-write 'utf8))
-        (set-buffer (find-file ical-filename))
-        (goto-char (point-max))
-        (insert "BEGIN:VCALENDAR")
-        (insert "\nPRODID:-//Emacs//NONSGML icalendar.el//EN")
-        (insert "\nVERSION:2.0")
-        (insert result)
-        (insert "\nEND:VCALENDAR\n")))
+      (save-current-buffer
+        (let ((coding-system-for-write 'utf-8))
+          (set-buffer (find-file ical-filename))
+          (goto-char (point-max))
+          (insert "BEGIN:VCALENDAR")
+          (insert "\nPRODID:-//Emacs//NONSGML icalendar.el//EN")
+          (insert "\nVERSION:2.0")
+          (insert result)
+          (insert "\nEND:VCALENDAR\n")
+          ;; save the diary file
+          (save-buffer))))
     found-error))
 
+;; subroutines
+(defun icalendar--convert-ordinary-to-ical (nonmarker entry-main)
+  "Convert \"ordinary\" diary entry to icalendar format.
+
+NONMARKER is a regular expression matching the start of non-marking
+entries.  ENTRY-MAIN is the first line of the diary entry."
+  (if (string-match (concat nonmarker
+                            "\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\)\\s-*"
+                            "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
+                            "\\("
+                            "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
+                            "\\)?"
+                            "\\s-*\\(.*\\)")
+                    entry-main)
+      (let* ((datetime (substring entry-main (match-beginning 1)
+                                  (match-end 1)))
+             (startisostring (icalendar--datestring-to-isodate
+                              datetime))
+             (endisostring (icalendar--datestring-to-isodate
+                            datetime 1))
+             (starttimestring (icalendar--diarytime-to-isotime
+                               (if (match-beginning 3)
+                                   (substring entry-main
+                                              (match-beginning 3)
+                                              (match-end 3))
+                                 nil)
+                               (if (match-beginning 4)
+                                   (substring entry-main
+                                              (match-beginning 4)
+                                              (match-end 4))
+                                 nil)))
+             (endtimestring (icalendar--diarytime-to-isotime
+                             (if (match-beginning 6)
+                                 (substring entry-main
+                                            (match-beginning 6)
+                                            (match-end 6))
+                               nil)
+                             (if (match-beginning 7)
+                                 (substring entry-main
+                                            (match-beginning 7)
+                                            (match-end 7))
+                               nil)))
+             (summary (icalendar--convert-string-for-export
+                       (substring entry-main (match-beginning 8)
+                                  (match-end 8)))))
+        (icalendar--dmsg "ordinary %s" entry-main)
+
+        (unless startisostring
+          (error "Could not parse date"))
+        (when starttimestring
+          (unless endtimestring
+            (let ((time
+                   (read (icalendar--rris "^T0?" ""
+                                          starttimestring))))
+              (setq endtimestring (format "T%06d"
+                                          (+ 10000 time))))))
+        (concat "\nDTSTART;"
+                (if starttimestring "VALUE=DATE-TIME:"
+                  "VALUE=DATE:")
+                startisostring
+                (or starttimestring "")
+                "\nDTEND;"
+                (if endtimestring "VALUE=DATE-TIME:"
+                  "VALUE=DATE:")
+                (if starttimestring
+                    startisostring
+                  endisostring)
+                (or endtimestring "")
+                "\nSUMMARY:"
+                summary))
+    ;; no match
+    nil))
+
+(defun icalendar--convert-weekly-to-ical (nonmarker entry-main)
+  "Convert weekly diary entry to icalendar format.
+
+NONMARKER is a regular expression matching the start of non-marking
+entries.  ENTRY-MAIN is the first line of the diary entry."
+  (if (and (string-match (concat nonmarker
+                                 "\\([a-z]+\\)\\s-+"
+                                 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)"
+                                 "\\([ap]m\\)?"
+                                 "\\(-0?"
+                                 "\\([1-9][0-9]?:[0-9][0-9]\\)"
+                                 "\\([ap]m\\)?\\)?"
+                                 "\\)?"
+                                 "\\s-*\\(.*\\)$")
+                         entry-main)
+           (icalendar--get-weekday-abbrev
+            (substring entry-main (match-beginning 1)
+                       (match-end 1))))
+      (let* ((day (icalendar--get-weekday-abbrev
+                   (substring entry-main (match-beginning 1)
+                              (match-end 1))))
+             (starttimestring (icalendar--diarytime-to-isotime
+                               (if (match-beginning 3)
+                                   (substring entry-main
+                                              (match-beginning 3)
+                                              (match-end 3))
+                                 nil)
+                               (if (match-beginning 4)
+                                   (substring entry-main
+                                              (match-beginning 4)
+                                              (match-end 4))
+                                 nil)))
+             (endtimestring (icalendar--diarytime-to-isotime
+                             (if (match-beginning 6)
+                                 (substring entry-main
+                                            (match-beginning 6)
+                                            (match-end 6))
+                               nil)
+                             (if (match-beginning 7)
+                                 (substring entry-main
+                                            (match-beginning 7)
+                                            (match-end 7))
+                               nil)))
+             (summary (icalendar--convert-string-for-export
+                       (substring entry-main (match-beginning 8)
+                                  (match-end 8)))))
+        (icalendar--dmsg "weekly %s" entry-main)
+
+        (when starttimestring
+          (unless endtimestring
+            (let ((time (read
+                         (icalendar--rris "^T0?" ""
+                                          starttimestring))))
+              (setq endtimestring (format "T%06d"
+                                          (+ 10000 time))))))
+        (concat "\nDTSTART;"
+                (if starttimestring
+                    "VALUE=DATE-TIME:"
+                  "VALUE=DATE:")
+                ;; find the correct week day,
+                ;; 1st january 2000 was a saturday
+                (format
+                 "200001%02d"
+                 (+ (icalendar--get-weekday-number day) 2))
+                (or starttimestring "")
+                "\nDTEND;"
+                (if endtimestring
+                    "VALUE=DATE-TIME:"
+                  "VALUE=DATE:")
+                (format
+                 "200001%02d"
+                 ;; end is non-inclusive!
+                 (+ (icalendar--get-weekday-number day)
+                    (if endtimestring 2 3)))
+                (or endtimestring "")
+                "\nSUMMARY:" summary
+                "\nRRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY="
+                day))
+    ;; no match
+    nil))
+
+(defun icalendar--convert-yearly-to-ical (nonmarker entry-main)
+  "Convert yearly diary entry to icalendar format.
+
+NONMARKER is a regular expression matching the start of non-marking
+entries.  ENTRY-MAIN is the first line of the diary entry."
+  (if (string-match (concat nonmarker
+                            (if european-calendar-style
+                                "0?\\([1-9]+[0-9]?\\)\\s-+\\([a-z]+\\)\\s-+"
+                              "\\([a-z]+\\)\\s-+0?\\([1-9]+[0-9]?\\)\\s-+")
+                            "\\*?\\s-*"
+                            "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
+                            "\\("
+                            "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
+                            "\\)?"
+                            "\\s-*\\([^0-9]+.*\\)$" ; must not match years
+                            )
+                    entry-main)
+      (let* ((daypos (if european-calendar-style 1 2))
+             (monpos (if european-calendar-style 2 1))
+             (day (read (substring entry-main
+                                   (match-beginning daypos)
+                                   (match-end daypos))))
+             (month (icalendar--get-month-number
+                     (substring entry-main
+                                (match-beginning monpos)
+                                (match-end monpos))))
+             (starttimestring (icalendar--diarytime-to-isotime
+                               (if (match-beginning 4)
+                                   (substring entry-main
+                                              (match-beginning 4)
+                                              (match-end 4))
+                                 nil)
+                               (if (match-beginning 5)
+                                   (substring entry-main
+                                              (match-beginning 5)
+                                              (match-end 5))
+                                 nil)))
+             (endtimestring (icalendar--diarytime-to-isotime
+                             (if (match-beginning 7)
+                                 (substring entry-main
+                                            (match-beginning 7)
+                                            (match-end 7))
+                               nil)
+                             (if (match-beginning 8)
+                                 (substring entry-main
+                                            (match-beginning 8)
+                                            (match-end 8))
+                               nil)))
+             (summary (icalendar--convert-string-for-export
+                       (substring entry-main (match-beginning 9)
+                                  (match-end 9)))))
+        (icalendar--dmsg "yearly %s" entry-main)
+
+        (when starttimestring
+          (unless endtimestring
+            (let ((time (read
+                         (icalendar--rris "^T0?" ""
+                                          starttimestring))))
+              (setq endtimestring (format "T%06d"
+                                          (+ 10000 time))))))
+        (concat "\nDTSTART;"
+                (if starttimestring "VALUE=DATE-TIME:"
+                  "VALUE=DATE:")
+                (format "1900%02d%02d" month day)
+                (or starttimestring "")
+                "\nDTEND;"
+                (if endtimestring "VALUE=DATE-TIME:"
+                  "VALUE=DATE:")
+                ;; end is not included! shift by one day
+                (icalendar--date-to-isodate
+                 (list month day 1900)
+                 (if endtimestring 0 1))
+                (or endtimestring "")
+                "\nSUMMARY:"
+                summary
+                "\nRRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH="
+                (format "%2d" month)
+                ";BYMONTHDAY="
+                (format "%2d" day)))
+    ;; no match
+    nil))
+
+(defun icalendar--convert-sexp-to-ical (nonmarker entry-main)
+  "Convert complex sexp diary entry to icalendar format -- unsupported!
+
+FIXME!
+
+NONMARKER is a regular expression matching the start of non-marking
+entries.  ENTRY-MAIN is the first line of the diary entry."
+  (if (string-match (concat nonmarker
+                            "%%(\\([^)]+\\))\\s-*\\(.*\\)")
+                    entry-main)
+      (progn
+        (icalendar--dmsg "diary-sexp %s" entry-main)
+        (error "Sexp-entries are not supported yet"))
+    ;; no match
+    nil))
+
+(defun icalendar--convert-block-to-ical (nonmarker entry-main)
+  "Convert block diary entry to icalendar format.
+
+NONMARKER is a regular expression matching the start of non-marking
+entries.  ENTRY-MAIN is the first line of the diary entry."
+  (if (string-match (concat nonmarker
+                            "%%(diary-block \\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\)"
+                            " +\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\))\\s-*"
+                            "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
+                            "\\("
+                            "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
+                            "\\)?"
+                            "\\s-*\\(.*\\)")
+                    entry-main)
+      (let* ((startstring (substring entry-main
+                                     (match-beginning 1)
+                                     (match-end 1)))
+             (endstring (substring entry-main
+                                   (match-beginning 2)
+                                   (match-end 2)))
+             (startisostring (icalendar--datestring-to-isodate
+                              startstring))
+             (endisostring (icalendar--datestring-to-isodate
+                            endstring))
+             (endisostring+1 (icalendar--datestring-to-isodate
+                              endstring 1))
+             (starttimestring (icalendar--diarytime-to-isotime
+                               (if (match-beginning 4)
+                                   (substring entry-main
+                                              (match-beginning 4)
+                                              (match-end 4))
+                                 nil)
+                               (if (match-beginning 5)
+                                   (substring entry-main
+                                              (match-beginning 5)
+                                              (match-end 5))
+                                 nil)))
+             (endtimestring (icalendar--diarytime-to-isotime
+                             (if (match-beginning 7)
+                                 (substring entry-main
+                                            (match-beginning 7)
+                                            (match-end 7))
+                               nil)
+                             (if (match-beginning 8)
+                                 (substring entry-main
+                                            (match-beginning 8)
+                                            (match-end 8))
+                               nil)))
+             (summary (icalendar--convert-string-for-export
+                       (substring entry-main (match-beginning 9)
+                                  (match-end 9)))))
+        (icalendar--dmsg "diary-block %s" entry-main)
+        (when starttimestring
+          (unless endtimestring
+            (let ((time
+                   (read (icalendar--rris "^T0?" ""
+                                          starttimestring))))
+              (setq endtimestring (format "T%06d"
+                                          (+ 10000 time))))))
+        (if starttimestring
+            ;; with time -> write rrule
+            (concat "\nDTSTART;VALUE=DATE-TIME:"
+                    startisostring
+                    starttimestring
+                    "\nDTEND;VALUE=DATE-TIME:"
+                    startisostring
+                    endtimestring
+                    "\nSUMMARY:"
+                    summary
+                    "\nRRULE:FREQ=DAILY;INTERVAL=1;UNTIL="
+                    endisostring)
+          ;; no time -> write long event
+          (concat "\nDTSTART;VALUE=DATE:" startisostring
+                  "\nDTEND;VALUE=DATE:" endisostring+1
+                  "\nSUMMARY:" summary)))
+    ;; no match
+    nil))
+
+(defun icalendar--convert-float-to-ical (nonmarker entry-main)
+  "Convert float diary entry to icalendar format -- unsupported!
+
+FIXME!
+
+NONMARKER is a regular expression matching the start of non-marking
+entries.  ENTRY-MAIN is the first line of the diary entry."
+  (if (string-match (concat nonmarker
+                            "%%(diary-float \\([^)]+\\))\\s-*\\(.*\\)")
+                    entry-main)
+      (progn
+        (icalendar--dmsg "diary-float %s" entry-main)
+        (error "`diary-float' is not supported yet"))
+    ;; no match
+    nil))
+
+(defun icalendar--convert-date-to-ical (nonmarker entry-main)
+  "Convert `diary-date' diary entry to icalendar format -- unsupported!
+
+FIXME!
+
+NONMARKER is a regular expression matching the start of non-marking
+entries.  ENTRY-MAIN is the first line of the diary entry."
+  (if (string-match (concat nonmarker
+                            "%%(diary-date \\([^)]+\\))\\s-*\\(.*\\)")
+                    entry-main)
+      (progn
+        (icalendar--dmsg "diary-date %s" entry-main)
+        (error "`diary-date' is not supported yet"))
+    ;; no match
+    nil))
+
+(defun icalendar--convert-cyclic-to-ical (nonmarker entry-main)
+  "Convert `diary-cyclic' diary entry to icalendar format.
+
+NONMARKER is a regular expression matching the start of non-marking
+entries.  ENTRY-MAIN is the first line of the diary entry."
+  (if (string-match (concat nonmarker
+                            "%%(diary-cyclic \\([^ ]+\\) +"
+                            "\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\))\\s-*"
+                            "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
+                            "\\("
+                            "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
+                            "\\)?"
+                            "\\s-*\\(.*\\)")
+                    entry-main)
+      (let* ((frequency (substring entry-main (match-beginning 1)
+                                   (match-end 1)))
+             (datetime (substring entry-main (match-beginning 2)
+                                  (match-end 2)))
+             (startisostring (icalendar--datestring-to-isodate
+                              datetime))
+             (endisostring (icalendar--datestring-to-isodate
+                            datetime))
+             (endisostring+1 (icalendar--datestring-to-isodate
+                              datetime 1))
+             (starttimestring (icalendar--diarytime-to-isotime
+                               (if (match-beginning 4)
+                                   (substring entry-main
+                                              (match-beginning 4)
+                                              (match-end 4))
+                                 nil)
+                               (if (match-beginning 5)
+                                   (substring entry-main
+                                              (match-beginning 5)
+                                              (match-end 5))
+                                 nil)))
+             (endtimestring (icalendar--diarytime-to-isotime
+                             (if (match-beginning 7)
+                                 (substring entry-main
+                                            (match-beginning 7)
+                                            (match-end 7))
+                               nil)
+                             (if (match-beginning 8)
+                                 (substring entry-main
+                                            (match-beginning 8)
+                                            (match-end 8))
+                               nil)))
+             (summary (icalendar--convert-string-for-export
+                       (substring entry-main (match-beginning 9)
+                                  (match-end 9)))))
+        (icalendar--dmsg "diary-cyclic %s" entry-main)
+        (when starttimestring
+          (unless endtimestring
+            (let ((time
+                   (read (icalendar--rris "^T0?" ""
+                                          starttimestring))))
+              (setq endtimestring (format "T%06d"
+                                          (+ 10000 time))))))
+        (concat "\nDTSTART;"
+                (if starttimestring "VALUE=DATE-TIME:"
+                  "VALUE=DATE:")
+                startisostring
+                (or starttimestring "")
+                "\nDTEND;"
+                (if endtimestring "VALUE=DATE-TIME:"
+                  "VALUE=DATE:")
+                (if endtimestring endisostring endisostring+1)
+                (or endtimestring "")
+                "\nSUMMARY:" summary
+                "\nRRULE:FREQ=DAILY;INTERVAL=" frequency
+                ;; strange: korganizer does not expect
+                ;; BYSOMETHING here...
+                ))
+    ;; no match
+    nil))
+
+(defun icalendar--convert-anniversary-to-ical (nonmarker entry-main)
+  "Convert `diary-anniversary' diary entry to icalendar format.
+
+NONMARKER is a regular expression matching the start of non-marking
+entries.  ENTRY-MAIN is the first line of the diary entry."
+  (if (string-match (concat nonmarker
+                            "%%(diary-anniversary \\([^)]+\\))\\s-*"
+                            "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
+                            "\\("
+                            "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
+                            "\\)?"
+                            "\\s-*\\(.*\\)")
+                    entry-main)
+      (let* ((datetime (substring entry-main (match-beginning 1)
+                                  (match-end 1)))
+             (startisostring (icalendar--datestring-to-isodate
+                              datetime))
+             (endisostring (icalendar--datestring-to-isodate
+                            datetime 1))
+             (starttimestring (icalendar--diarytime-to-isotime
+                               (if (match-beginning 3)
+                                   (substring entry-main
+                                              (match-beginning 3)
+                                              (match-end 3))
+                                 nil)
+                               (if (match-beginning 4)
+                                   (substring entry-main
+                                              (match-beginning 4)
+                                              (match-end 4))
+                                 nil)))
+             (endtimestring (icalendar--diarytime-to-isotime
+                             (if (match-beginning 6)
+                                 (substring entry-main
+                                            (match-beginning 6)
+                                            (match-end 6))
+                               nil)
+                             (if (match-beginning 7)
+                                 (substring entry-main
+                                            (match-beginning 7)
+                                            (match-end 7))
+                               nil)))
+             (summary (icalendar--convert-string-for-export
+                       (substring entry-main (match-beginning 8)
+                                  (match-end 8)))))
+        (icalendar--dmsg "diary-anniversary %s" entry-main)
+        (when starttimestring
+          (unless endtimestring
+            (let ((time
+                   (read (icalendar--rris "^T0?" ""
+                                          starttimestring))))
+              (setq endtimestring (format "T%06d"
+                                          (+ 10000 time))))))
+        (concat "\nDTSTART;"
+                (if starttimestring "VALUE=DATE-TIME:"
+                  "VALUE=DATE:")
+                startisostring
+                (or starttimestring "")
+                "\nDTEND;"
+                (if endtimestring "VALUE=DATE-TIME:"
+                  "VALUE=DATE:")
+                endisostring
+                (or endtimestring "")
+                "\nSUMMARY:" summary
+                "\nRRULE:FREQ=YEARLY;INTERVAL=1"
+                ;; the following is redundant,
+                ;; but korganizer seems to expect this... ;(
+                ;; and evolution doesn't understand it... :(
+                ;; so... who is wrong?!
+                ";BYMONTH="
+                (substring startisostring 4 6)
+                ";BYMONTHDAY="
+                (substring startisostring 6 8)))
+    ;; no match
+    nil))
+
 ;; ======================================================================
 ;; Import -- convert icalendar to emacs-diary
 ;; ======================================================================
 
-;; User function
+;;;###autoload
 (defun icalendar-import-file (ical-filename diary-filename
                                             &optional non-marking)
   "Import a iCalendar file and append to a diary file.
@@ -1046,7 +1307,7 @@ Argument DIARY-FILENAME input `diary-file'.
 Optional argument NON-MARKING determines whether events are created as
 non-marking or not."
   (interactive "fImport iCalendar data from file: 
-Finto diary file:
+Finto diary file: 
 p")
   ;; clean up the diary file
   (save-current-buffer
@@ -1054,7 +1315,7 @@ p")
     (set-buffer (find-file ical-filename))
     (icalendar-import-buffer diary-filename t non-marking)))
 
-;; User function
+;;;###autoload
 (defun icalendar-import-buffer (&optional diary-file do-not-ask
                                           non-marking)
   "Extract iCalendar events from current buffer.
@@ -1070,8 +1331,9 @@ DO-NOT-ASK is set to t, so that you are asked fore each event.
 NON-MARKING determines whether diary events are created as
 non-marking.
 
-This function attempts to notify about problems that occur when
-reading, parsing, or converting iCalendar data!"
+Return code t means that importing worked well, return code nil
+means that an error has occured.  Error messages will be in the
+buffer `*icalendar-errors*'."
   (interactive)
   (save-current-buffer
     ;; prepare ical
@@ -1092,26 +1354,23 @@ reading, parsing, or converting iCalendar data!"
                              ical-contents
                              diary-file do-not-ask non-marking))
           (when diary-file
-            ;; save the diary file
-            (save-current-buffer
-              (set-buffer (find-buffer-visiting diary-file))
-              (save-buffer)))
+            ;; save the diary file if it is visited already
+            (let ((b (find-buffer-visiting diary-file)))
+              (when b
+                (save-current-buffer
+                  (set-buffer b)
+                  (save-buffer)))))
           (message "Converting icalendar...done")
-          (if (and ical-errors (y-or-n-p
-                                (concat "Something went wrong -- "
-                                        "do you want to see the "
-                                        "error log? ")))
-              (switch-to-buffer " *icalendar-errors*")))
+          ;; return t if no error occured
+          (not ical-errors))
       (message
-       "Current buffer does not contain icalendar contents!"))))
+       "Current buffer does not contain icalendar contents!")
+      ;; return nil, i.e. import did not work
+      nil)))
 
 (defalias 'icalendar-extract-ical-from-buffer 'icalendar-import-buffer)
 (make-obsolete 'icalendar-extract-ical-from-buffer 'icalendar-import-buffer)
 
-;; ======================================================================
-;; private area
-;; ======================================================================
-
 (defun icalendar--format-ical-event (event)
   "Create a string representation of an iCalendar EVENT."
   (let ((string icalendar-import-format)
@@ -1149,7 +1408,7 @@ whether to actually import it.  NON-MARKING determines whether diary
 events are created as non-marking.
 This function attempts to return t if something goes wrong.  In this
 case an error string which describes all the errors and problems is
-written into the buffer ` *icalendar-errors*'."
+written into the buffer `*icalendar-errors*'."
   (let* ((ev (icalendar--all-events ical-list))
          (error-string "")
          (event-ok t)
@@ -1161,16 +1420,16 @@ written into the buffer ` *icalendar-errors*'."
       (setq ev (cdr ev))
       (setq event-ok nil)
       (condition-case error-val
-          (let* ((dtstart (icalendar--decode-isodatetime
-                           (icalendar--get-event-property e 'DTSTART)))
-                 (start-d (calendar-date-string
-                           (icalendar--datetime-to-noneuropean-date
-                            dtstart)
-                           t t))
-                 (start-t (icalendar--datetime-to-colontime dtstart))
-                 (dtend (icalendar--decode-isodatetime
-                         (icalendar--get-event-property e 'DTEND)))
+          (let* ((dtstart (icalendar--get-event-property e 'DTSTART))
+                 (dtstart-dec (icalendar--decode-isodatetime dtstart))
+                 (start-d (icalendar--datetime-to-diary-date
+                           dtstart-dec))
+                 (start-t (icalendar--datetime-to-colontime dtstart-dec))
+                 (dtend (icalendar--get-event-property e 'DTEND))
+                 (dtend-dec (icalendar--decode-isodatetime dtend))
+                 (dtend-1-dec (icalendar--decode-isodatetime dtend -1))
                  end-d
+                 end-1-d
                  end-t
                  (subject (icalendar--convert-string-for-import
                            (or (icalendar--get-event-property e 'SUMMARY)
@@ -1178,96 +1437,50 @@ written into the buffer ` *icalendar-errors*'."
                  (rrule (icalendar--get-event-property e 'RRULE))
                  (rdate (icalendar--get-event-property e 'RDATE))
                  (duration (icalendar--get-event-property e 'DURATION)))
-            (icalendar--dmsg "%s: %s" start-d subject)
+            (icalendar--dmsg "%s: `%s'" start-d subject)
+            ;; check whether start-time is missing
+            (if  (and dtstart
+                      (string=
+                       (cadr (icalendar--get-event-property-attributes
+                              e 'DTSTART))
+                       "DATE"))
+                (setq start-t nil))
             (when duration
-              (let ((dtend2 (icalendar--add-decoded-times
-                             dtstart
-                             (icalendar--decode-isoduration duration))))
-                (if (and dtend (not (eq dtend dtend2)))
+              (let ((dtend-dec-d (icalendar--add-decoded-times
+                                  dtstart-dec
+                                  (icalendar--decode-isoduration duration)))
+                    (dtend-1-dec-d (icalendar--add-decoded-times
+                                    dtstart-dec
+                                    (icalendar--decode-isoduration duration
+                                                                   t))))
+                (if (and dtend-dec (not (eq dtend-dec dtend-dec-d)))
                     (message "Inconsistent endtime and duration for %s"
                              subject))
-                (setq dtend dtend2)))
-            (setq end-d (if dtend
-                            (calendar-date-string
-                             (icalendar--datetime-to-noneuropean-date
-                              dtend)
-                             t t)
+                (setq dtend-dec dtend-dec-d)
+                (setq dtend-1-dec dtend-1-dec-d)))
+            (setq end-d (if dtend-dec
+                            (icalendar--datetime-to-diary-date dtend-dec)
                           start-d))
-            (setq end-t (if dtend
-                            (icalendar--datetime-to-colontime dtend)
+            (setq end-1-d (if dtend-1-dec
+                              (icalendar--datetime-to-diary-date dtend-1-dec)
+                            start-d))
+            (setq end-t (if (and
+                             dtend-dec
+                             (not (string=
+                                   (cadr
+                                    (icalendar--get-event-property-attributes
+                                     e 'DTEND))
+                                   "DATE")))
+                            (icalendar--datetime-to-colontime dtend-dec)
                           start-t))
             (icalendar--dmsg "start-d: %s, end-d: %s" start-d end-d)
             (cond
              ;; recurring event
              (rrule
-              (icalendar--dmsg "recurring event")
-              (let* ((rrule-props (icalendar--split-value rrule))
-                     (frequency (car (cdr (assoc 'FREQ rrule-props))))
-                     (until (car (cdr (assoc 'UNTIL rrule-props))))
-                     (interval  (read (car (cdr (assoc 'INTERVAL
-                                                       rrule-props))))))
-                (cond ((string-equal frequency "WEEKLY")
-                       (if (not start-t)
-                           (progn
-                             ;; weekly and all-day
-                             (icalendar--dmsg "weekly all-day")
-                             (setq diary-string
-                                   (format
-                                    "%%%%(diary-cyclic %d %s)"
-                                    (* interval 7)
-                                    (icalendar--datetime-to-european-date
-                                     dtstart))))
-                         ;; weekly and not all-day
-                         (let* ((byday (cadr (assoc 'BYDAY rrule-props)))
-                                (weekday
-                                 (icalendar--get-weekday-number byday)))
-                           (icalendar--dmsg "weekly not-all-day")
-                           (if (> weekday -1)
-                               (setq diary-string
-                                     (format "%s %s%s%s"
-                                             (aref calendar-day-name-array
-                                                   weekday)
-                                             start-t (if end-t "-" "")
-                                             (or end-t "")))
-                             ;; FIXME!!!!
-                             ;; DTSTART;VALUE=DATE-TIME:20030919T090000
-                             ;; DTEND;VALUE=DATE-TIME:20030919T113000
-                             (setq diary-string
-                                   (format
-                                    "%%%%(diary-cyclic %s %s) %s%s%s"
-                                    (* interval 7)
-                                    (icalendar--datetime-to-european-date
-                                     dtstart)
-                                    start-t (if end-t "-" "") (or end-t ""))))
-                           (setq event-ok t))))
-                      ;; yearly
-                      ((string-equal frequency "YEARLY")
-                       (icalendar--dmsg "yearly")
-                       (setq diary-string
-                             (format
-                              "%%%%(diary-anniversary %s)"
-                              (icalendar--datetime-to-european-date dtstart)))
-                       (setq event-ok t))
-                      ;; FIXME: war auskommentiert:
-                      ((and (string-equal frequency "DAILY")
-                            ;;(not (string= start-d end-d))
-                            ;;(not start-t)
-                            ;;(not end-t)
-                            )
-                       (let ((ds (icalendar--datetime-to-noneuropean-date
-                                  (icalendar--decode-isodatetime
-                                   (icalendar--get-event-property e
-                                                                  'DTSTART))))
-                             (de (icalendar--datetime-to-noneuropean-date
-                                  (icalendar--decode-isodatetime
-                                   until))))
-                         (setq diary-string
-                               (format
-                                "%%%%(diary-block %d %d %d  %d %d %d)"
-                                (nth 1 ds) (nth 0 ds) (nth 2 ds)
-                                (nth 1 de) (nth 0 de) (nth 2 de))))
-                       (setq event-ok t)))
-                ))
+              (setq diary-string
+                    (icalendar--convert-recurring-to-diary e dtstart-dec start-t
+                                                           end-t))
+              (setq event-ok t))
              (rdate
               (icalendar--dmsg "rdate event")
               (setq diary-string "")
@@ -1277,31 +1490,24 @@ written into the buffer ` *icalendar-errors*'."
                                       (format "......"))))
                       (icalendar--split-value rdate)))
              ;; non-recurring event
-             ;; long event
+             ;; all-day event
              ((not (string= start-d end-d))
-              (icalendar--dmsg "non-recurring event")
-              (let ((ds (icalendar--datetime-to-noneuropean-date dtstart))
-                    (de (icalendar--datetime-to-noneuropean-date dtend)))
-                (setq diary-string
-                      (format "%%%%(diary-block %d %d %d   %d %d %d)"
-                              (nth 1 ds) (nth 0 ds) (nth 2 ds)
-                              (nth 1 de) (nth 0 de) (nth 2 de))))
+              (setq diary-string
+                    (icalendar--convert-non-recurring-all-day-to-diary
+                     e start-d end-1-d))
               (setq event-ok t))
              ;; not all-day
              ((and start-t (or (not end-t)
                                (not (string= start-t end-t))))
-              (icalendar--dmsg "not all day event")
-              (cond (end-t
-                     (setq diary-string (format "%s %s-%s" start-d
-                                                start-t end-t)))
-                    (t
-                     (setq diary-string (format "%s %s" start-d
-                                                start-t))))
+              (setq diary-string
+                    (icalendar--convert-non-recurring-not-all-day-to-diary
+                     e dtstart-dec dtend-dec start-t end-t))
               (setq event-ok t))
              ;; all-day event
              (t
               (icalendar--dmsg "all day event")
-              (setq diary-string start-d)
+              (setq diary-string (icalendar--datetime-to-diary-date
+                                  dtstart-dec "/"))
               (setq event-ok t)))
             ;; add all other elements unless the user doesn't want to have
             ;; them
@@ -1318,20 +1524,247 @@ written into the buffer ` *icalendar-errors*'."
               (setq error-string
                     (format "%s\nCannot handle this event:%s"
                             error-string e))))
+        ;; FIXME: inform user about ignored event properties
         ;; handle errors
         (error
          (message "Ignoring event \"%s\"" e)
          (setq found-error t)
-         (setq error-string (format "%s\nCannot handle this event: %s"
-                                    error-string e)))))
+         (setq error-string (format "%s\n%s\nCannot handle this event: %s"
+                                    error-val error-string e))
+         (message error-string))))
     (if found-error
         (save-current-buffer
-          (set-buffer (get-buffer-create " *icalendar-errors*"))
+          (set-buffer (get-buffer-create "*icalendar-errors*"))
           (erase-buffer)
           (insert error-string)))
     (message "Converting icalendar...done")
     found-error))
 
+;; subroutines for importing
+(defun icalendar--convert-recurring-to-diary (e dtstart-dec start-t end-t)
+  "Convert recurring icalendar event E to diary format.
+
+DTSTART-DEC is the DTSTART property of E.
+START-T is the event's start time in diary format.
+END-T is the event's end time in diary format."
+  (icalendar--dmsg "recurring event")
+  (let* ((rrule        (icalendar--get-event-property e 'RRULE))
+         (rrule-props  (icalendar--split-value rrule))
+         (frequency    (cadr (assoc 'FREQ rrule-props)))
+         (until        (cadr (assoc 'UNTIL rrule-props)))
+         (count        (cadr (assoc 'COUNT rrule-props)))
+         (interval     (read (or (cadr (assoc 'INTERVAL rrule-props)) "1")))
+         (dtstart-conv (icalendar--datetime-to-diary-date dtstart-dec))
+         (until-conv   (icalendar--datetime-to-diary-date
+                        (icalendar--decode-isodatetime until)))
+         (until-1-conv (icalendar--datetime-to-diary-date
+                        (icalendar--decode-isodatetime until -1)))
+         (result  ""))
+
+    ;; FIXME FIXME interval!!!!!!!!!!!!!
+
+    (when count
+      (if until
+          (message "Must not have UNTIL and COUNT -- ignoring COUNT element!")
+        (let ((until-1 0))
+          (cond ((string-equal frequency "DAILY")
+                 (setq until (icalendar--add-decoded-times
+                              dtstart-dec 
+                              (list 0 0 0 (* (read count) interval) 0 0)))
+                 (setq until-1 (icalendar--add-decoded-times
+                                dtstart-dec
+                                (list 0 0 0 (* (- (read count) 1) interval)
+                                      0 0)))
+                 )
+                ((string-equal frequency "WEEKLY")
+                 (setq until (icalendar--add-decoded-times
+                              dtstart-dec
+                              (list 0 0 0 (* (read count) 7 interval) 0 0)))
+                 (setq until-1 (icalendar--add-decoded-times
+                                dtstart-dec
+                                (list 0 0 0 (* (- (read count) 1) 7
+                                               interval) 0 0)))
+                 )
+                ((string-equal frequency "MONTHLY")
+                 (setq until (icalendar--add-decoded-times
+                              dtstart-dec (list 0 0 0 0 (* (- (read count) 1)
+                                                           interval) 0)))
+                 (setq until-1 (icalendar--add-decoded-times
+                                dtstart-dec (list 0 0 0 0 (* (- (read count) 1)
+                                                             interval) 0)))
+                 )
+                ((string-equal frequency "YEARLY")
+                 (setq until (icalendar--add-decoded-times
+                              dtstart-dec (list 0 0 0 0 0 (* (- (read count) 1)
+                                                             interval))))
+                 (setq until-1 (icalendar--add-decoded-times
+                                dtstart-dec
+                                (list 0 0 0 0 0 (* (- (read count) 1)
+                                                   interval))))
+                 )
+                (t
+                 (message "Cannot handle COUNT attribute for `%s' events."
+                          frequency)))
+          (setq until-conv (icalendar--datetime-to-diary-date until))
+          (setq until-1-conv (icalendar--datetime-to-diary-date until-1))
+          ))
+      )
+    (cond ((string-equal frequency "WEEKLY")
+           (if (not start-t)
+               (progn
+                 ;; weekly and all-day
+                 (icalendar--dmsg "weekly all-day")
+                 (if until
+                     (setq result
+                           (format
+                            (concat "%%%%(and "
+                                    "(diary-cyclic %d %s) "
+                                    "(diary-block %s %s))")
+                            (* interval 7)
+                            dtstart-conv
+                            dtstart-conv
+                            (if count until-1-conv until-conv)
+                            ))
+                   (setq result
+                         (format "%%%%(and (diary-cyclic %d %s))"
+                                 (* interval 7)
+                                 dtstart-conv))))
+             ;; weekly and not all-day
+             (let* ((byday (cadr (assoc 'BYDAY rrule-props)))
+                    (weekday
+                     (icalendar--get-weekday-number byday)))
+               (icalendar--dmsg "weekly not-all-day")
+               (if until
+                   (setq result
+                         (format
+                          (concat "%%%%(and "
+                                  "(diary-cyclic %d %s) "
+                                  "(diary-block %s %s)) "
+                                  "%s%s%s")
+                          (* interval 7)
+                          dtstart-conv
+                          dtstart-conv
+                          until-conv
+                          (or start-t "")
+                          (if end-t "-" "") (or end-t "")))
+                 ;; no limit
+                 ;; FIXME!!!!
+                 ;; DTSTART;VALUE=DATE-TIME:20030919T090000
+                 ;; DTEND;VALUE=DATE-TIME:20030919T113000
+                 (setq result
+                       (format
+                        "%%%%(and (diary-cyclic %s %s)) %s%s%s"
+                        (* interval 7)
+                        dtstart-conv
+                        (or start-t "")
+                        (if end-t "-" "") (or end-t "")))))))
+          ;; yearly
+          ((string-equal frequency "YEARLY")
+           (icalendar--dmsg "yearly")
+           (if until
+               (setq result (format
+                             (concat "%%%%(and (diary-date %s %s t) "
+                                     "(diary-block %s %s)) %s%s%s")
+                             (if european-calendar-style (nth 3 dtstart-dec)
+                               (nth 4 dtstart-dec))
+                             (if european-calendar-style (nth 4 dtstart-dec)
+                               (nth 3 dtstart-dec))
+                             dtstart-conv
+                             until-conv
+                             (or start-t "")
+                             (if end-t "-" "") (or end-t "")))
+             (setq result (format
+                           "%%%%(and (diary-anniversary %s)) %s%s%s"
+                           dtstart-conv
+                           (or start-t "")
+                           (if end-t "-" "") (or end-t "")))))
+          ;; monthly
+          ((string-equal frequency "MONTHLY")
+           (icalendar--dmsg "monthly")
+           (setq result
+                 (format
+                  "%%%%(and (diary-date %s %s %s) (diary-block %s %s)) %s%s%s"
+                  (if european-calendar-style (nth 3 dtstart-dec) "t")
+                  (if european-calendar-style "t" (nth 3 dtstart-dec))
+                  "t"
+                  dtstart-conv
+                  (if until
+                      until-conv
+                    "1 1 9999") ;; FIXME: should be unlimited
+                  (or start-t "")
+                  (if end-t "-" "") (or end-t ""))))
+          ;; daily
+          ((and (string-equal frequency "DAILY"))
+           (if until
+               (setq result
+                     (format
+                      (concat "%%%%(and (diary-cyclic %s %s) "
+                              "(diary-block %s %s)) %s%s%s")
+                      interval dtstart-conv dtstart-conv
+                      (if count until-1-conv until-conv)
+                      (or start-t "")
+                      (if end-t "-" "") (or end-t "")))
+             (setq result
+                   (format
+                    "%%%%(and (diary-cyclic %s %s)) %s%s%s"
+                    interval
+                    dtstart-conv
+                    (or start-t "")
+                    (if end-t "-" "") (or end-t ""))))))
+    ;; Handle exceptions from recurrence rules
+    (let ((ex-dates (icalendar--get-event-properties e 'EXDATE)))
+      (while ex-dates
+        (let* ((ex-start (icalendar--decode-isodatetime
+                          (car ex-dates)))
+               (ex-d (icalendar--datetime-to-diary-date
+                      ex-start)))
+          (setq result
+                (icalendar--rris "^%%(\\(and \\)?"
+                                 (format
+                                  "%%%%(and (not (diary-date %s)) "
+                                  ex-d)
+                                 result)))
+        (setq ex-dates (cdr ex-dates))))
+    ;; FIXME: exception rules are not recognized
+    (if (icalendar--get-event-property e 'EXRULE)
+        (setq result
+              (concat result
+                      "\n Exception rules: "
+                      (icalendar--get-event-properties
+                       e 'EXRULE))))
+    result))
+
+(defun icalendar--convert-non-recurring-all-day-to-diary (event start-d end-d)
+  "Convert non-recurring icalendar EVENT to diary format.
+
+DTSTART is the decoded DTSTART property of E.
+Argument START-D gives the first day.
+Argument END-D gives the last day."
+  (icalendar--dmsg "non-recurring all-day event")
+  (format "%%%%(and (diary-block %s %s))" start-d end-d))
+
+(defun icalendar--convert-non-recurring-not-all-day-to-diary (event dtstart-dec
+                                                                    dtend-dec
+                                                                    start-t
+                                                                    end-t)
+  "Convert recurring icalendar EVENT to diary format.
+
+DTSTART-DEC is the decoded DTSTART property of E.
+DTEND-DEC is the decoded DTEND property of E.
+START-T is the event's start time in diary format.
+END-T is the event's end time in diary format."
+  (icalendar--dmsg "not all day event")
+  (cond (end-t
+         (format "%s %s-%s"
+                 (icalendar--datetime-to-diary-date
+                  dtstart-dec "/")
+                 start-t end-t))
+        (t
+         (format "%s %s"
+                 (icalendar--datetime-to-diary-date
+                  dtstart-dec "/")
+                 start-t))))
+
 (defun icalendar--add-diary-entry (string diary-file non-marking
                                           &optional subject)
   "Add STRING to the diary file DIARY-FILE.
@@ -1340,7 +1773,7 @@ determines whether diary events are created as non-marking.  If
 SUBJECT is not nil it must be a string that gives the subject of the
 entry.  In this case the user will be asked whether he wants to insert
 the entry."
-  (when (or (not subject)               ;
+  (when (or (not subject)
             (y-or-n-p (format "Add appointment for `%s' to diary? "
                               subject)))
     (when subject