New file, from Ulf Jasper <ulf.jasper@web.de>.
authorGlenn Morris <rgm@gnu.org>
Tue, 5 Oct 2004 22:29:16 +0000 (22:29 +0000)
committerGlenn Morris <rgm@gnu.org>
Tue, 5 Oct 2004 22:29:16 +0000 (22:29 +0000)
lisp/calendar/icalendar.el [new file with mode: 0644]

diff --git a/lisp/calendar/icalendar.el b/lisp/calendar/icalendar.el
new file mode 100644 (file)
index 0000000..86a4021
--- /dev/null
@@ -0,0 +1,1298 @@
+;;; icalendar.el --- iCalendar implementation
+
+;; Copyright (C) 2002, 2003, 2004  Free Software Foundation, Inc.
+
+;; Author:   Ulf Jasper <ulf.jasper@web.de>
+;; Created:  August 2002
+;; Keywords: calendar
+;; Human-Keywords: calendar, diary, iCalendar, vCalendar
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;; This package is documented in the Emacs Manual.
+
+
+;;; History:
+
+;;  0.06  Bugfixes regarding icalendar-import-format-*.
+;;        Fix in icalendar-convert-diary-to-ical -- thanks to Philipp Grau.
+
+;;  0.05: New import format scheme: Replaced icalendar-import-prefix-*,
+;;        icalendar-import-ignored-properties, and
+;;        icalendar-import-separator with icalendar-import-format(-*).
+;;        icalendar-import-file and icalendar-convert-diary-to-ical
+;;        have an extra parameter which should prevent them from
+;;        erasing their target files (untested!).
+;;        Tested with Emacs 21.3.2
+
+;;  0.04: Bugfix: import: double quoted param values did not work
+;;        Read DURATION property when importing.
+;;        Added parameter icalendar-duration-correction.
+
+;;  0.03: Export takes care of european-calendar-style.
+;;        Tested with Emacs 21.3.2 and XEmacs 21.4.12
+
+;;  0.02: Should work in XEmacs now.  Thanks to Len Trigg for the
+;;        XEmacs patches!
+;;        Added exporting from Emacs diary to ical.
+;;        Some bugfixes, after testing with calendars from
+;;        http://icalshare.com.
+;;        Tested with Emacs 21.3.2 and XEmacs 21.4.12
+
+;;  0.01: First published version.  Trial version.  Alpha version.
+
+;; ======================================================================
+;; To Do:
+
+;;  * Import from ical:
+;;    + Need more properties for icalendar-import-format
+;;    + check vcalendar version
+;;    + check (unknown) elements
+;;    + recurring events!
+;;    + works for european style calendars only! Does it?
+;;    + alarm
+;;    + exceptions in recurring events
+;;    + the parser is too soft
+;;    + error log is incomplete
+;;    + nice to have: #include "webcal://foo.com/some-calendar.ics"
+
+;;  * Export into ical
+;;    + diary-date, diary-float, and self-made sexp entries are not
+;;      understood
+;;    + timezones, currently all times are local!
+
+;;  * Other things
+;;    + defcustom icalendar-import-ignored-properties does not work with
+;;      XEmacs.
+;;    + clean up all those date/time parsing functions
+;;    + Handle todo items?
+;;    + Check iso 8601 for datetime and period
+;;    + Which chars to (un)escape?
+;;    + Time to find out how the profiler works?
+
+
+;;; Code:
+
+(defconst icalendar-version 0.06
+  "Version number of icalendar.el.")
+
+;; ======================================================================
+;; Customizables
+;; ======================================================================
+(defgroup icalendar nil
+  "Icalendar support."
+  :prefix "icalendar-"
+  :group 'calendar)
+
+(defcustom icalendar-import-format
+  "%s%d%l%o"
+  "Format string for importing events from iCalendar into Emacs diary.
+This string defines how iCalendar events are inserted into diary
+file.  Meaning of the specifiers:
+%d Description, see `icalendar-import-format-description'
+%l Location, see `icalendar-import-format-location'
+%o Organizer, see `icalendar-import-format-organizer'
+%s Subject, see `icalendar-import-format-subject'"
+  :type 'string
+  :group 'icalendar)
+
+(defcustom icalendar-import-format-subject
+  "%s"
+  "Format string defining how the subject element is formatted.
+This applies only if the subject is not empty! `%s' is replaced
+by the subject."
+  :type 'string
+  :group 'icalendar)
+
+(defcustom icalendar-import-format-description
+  "\n Desc: %s"
+  "Format string defining how the description element is formatted.
+This applies only if the description is not empty! `%s' is
+replaced by the description."
+  :type 'string
+  :group 'icalendar)
+
+(defcustom icalendar-import-format-location
+  "\n Location: %s"
+  "Format string defining how the location element is formatted.
+This applies only if the location is not empty! `%s' is replaced
+by the location."
+  :type 'string
+  :group 'icalendar)
+
+(defcustom icalendar-import-format-organizer
+  "\n Organizer: %s"
+  "Format string defining how the organizer element is formatted.
+This applies only if the organizer is not empty! `%s' is
+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)
+
+
+;; ======================================================================
+;; NO USER SERVICABLE PARTS BELOW THIS LINE
+;; ======================================================================
+
+(defconst icalendar-weekdayabbrev-table
+  '(("mon\\(day\\)?"    . "MO")
+    ("tue\\(sday\\)?"   . "TU")
+    ("wed\\(nesday\\)?" . "WE")
+    ("thu\\(rsday\\)?"  . "TH")
+    ("fri\\(day\\)?"    . "FR")
+    ("sat\\(urday\\)?"  . "SA")
+    ("sun\\(day\\)?"    . "SU"))
+  "Translation table for weekdays.")
+
+(defconst icalendar-monthnumber-table
+  '(("^jan\\(uar\\)?y?$"       . 1)
+    ("^feb\\(ruar\\)?y?$"      . 2)
+    ("^mar\\(ch\\)?\\|märz?$" . 3)
+    ("^apr\\(il\\)?$"          . 4)
+    ("^ma[iy]$"                . 5)
+    ("^jun[ie]?$"              . 6)
+    ("^jul[iy]?$"              . 7)
+    ("^aug\\(ust\\)?$"         . 8)
+    ("^sep\\(tember\\)?$"      . 9)
+    ("^o[ck]t\\(ober\\)?$"     . 10)
+    ("^nov\\(ember\\)?$"       . 11)
+    ("^de[cz]\\(ember\\)?$"    . 12))
+  "Regular expressions for month names.
+Currently this matches only German and English.")
+
+(defvar icalendar-debug nil ".")
+
+;; ======================================================================
+;; all the other libs we need
+;; ======================================================================
+(require 'calendar)
+(require 'appt)
+
+;; ======================================================================
+;; Core functionality
+;; Functions for parsing icalendars, importing and so on
+;; ======================================================================
+
+(defun icalendar-get-unfolded-buffer (folded-ical-buffer)
+  "Return a new buffer containing the unfolded contents of a buffer.
+Folding is the iCalendar way of wrapping long lines.  In the
+created buffer all occurrences of CR LF BLANK are replaced by the
+empty string.  Argument FOLDED-ICAL-BUFFER is the unfolded input
+buffer."
+  (let ((unfolded-buffer (get-buffer-create " *icalendar-work*")))
+    (save-current-buffer
+      (set-buffer unfolded-buffer)
+      (erase-buffer)
+      (insert-buffer folded-ical-buffer)
+      (while (re-search-forward "\r?\n[ \t]" nil t)
+        (replace-match "" nil nil))
+      )
+    unfolded-buffer))
+
+;; Replace regexp RE with RP in string ST and return the new string.
+;; This is here for compatibility with XEmacs.
+(defsubst icalendar-rris (re rp st)
+  ;; XEmacs:
+  (if (fboundp 'replace-in-string)
+      (save-match-data ;; apparently XEmacs needs save-match-data
+        (replace-in-string st re rp))
+    ;; Emacs:
+    (replace-regexp-in-string re rp st)))
+
+(defun icalendar-read-element (invalue inparams)
+  "Recursively read the next iCalendar element in the current buffer.
+INVALUE gives the current iCalendar element we are reading.
+INPARAMS gives the current parameters.....
+This function calls itself recursively for each nested calendar element
+it finds"
+  (let (element children line name params param param-name param-value
+                value
+        (continue t))
+    (setq children '())
+    (while (and continue
+                (re-search-forward "^\\([A-Za-z0-9-]+\\)[;:]" nil t))
+      (setq name (intern (match-string 1)))
+      (backward-char 1)
+      (setq params '())
+      (setq line '())
+      (while (looking-at ";")
+        (re-search-forward ";\\([A-Za-z0-9-]+\\)=" nil nil)
+        (setq param-name (intern (match-string 1)))
+        (re-search-forward "\\(\\([^;,:\"]+\\)\\|\"\\([^\"]+\\)\"\\)[;:]"
+                           nil t)
+        (backward-char 1)
+        (setq param-value (or (match-string 2) (match-string 3)))
+        (setq param (list param-name param-value))
+        (while (looking-at ",")
+          (re-search-forward "\\(\\([^;,:]+\\)\\|\"\\([^\"]+\\)\"\\)"
+                             nil t)
+          (if (match-string 2)
+              (setq param-value (match-string 2))
+            (setq param-value (match-string 3)))
+          (setq param (append param param-value)))
+        (setq params (append params param)))
+      (unless (looking-at ":")
+        (error "Oops"))
+      (forward-char 1)
+      (re-search-forward  "\\(.*\\)\\(\r?\n[ \t].*\\)*" nil t)
+      (setq value (icalendar-rris "\r?\n[ \t]" "" (match-string 0)))
+      (setq line (list name params value))
+      (cond ((eq name 'BEGIN)
+             (setq children
+                   (append children
+                           (list (icalendar-read-element (intern value)
+                                                         params)))))
+            ((eq name 'END)
+             (setq continue nil))
+            (t
+             (setq element (append element (list line))))))
+    (if invalue
+        (list invalue inparams element children)
+      children)))
+
+;; ======================================================================
+;; helper functions for examining events
+;; ======================================================================
+
+(defsubst icalendar-get-all-event-properties (event)
+  "Return the list of properties in this EVENT."
+  (car (cddr event)))
+
+(defun icalendar-get-event-property (event prop)
+  "For the given EVENT return the value of the property PROP."
+  (catch 'found
+    (let ((props (car (cddr event))) pp)
+      (while props
+        (setq pp (car props))
+        (if (eq (car pp) prop)
+            (throw 'found (car (cddr pp))))
+        (setq props (cdr props))))
+    nil))
+
+(defun icalendar-set-event-property (event prop new-value)
+  "For the given EVENT set the property PROP to the value NEW-VALUE."
+  (catch 'found
+    (let ((props (car (cddr event))) pp)
+      (while props
+        (setq pp (car props))
+        (when (eq (car pp) prop)
+          (setcdr (cdr pp) new-value)
+          (throw 'found (car (cddr pp))))
+        (setq props (cdr props)))
+      (setq props (car (cddr event)))
+      (setcar (cddr event)
+              (append props (list (list prop nil new-value)))))))
+
+(defun icalendar-get-children (node name)
+  "Return all children of the given NODE which have a name NAME.
+For instance the VCALENDAR node can have VEVENT children as well as VTODO
+children."
+  (let ((result nil)
+        (children (cadr (cddr node))))
+    (when (eq (car node) name)
+      (setq result node))
+    ;;(message "%s" node)
+    (when children
+      (let ((subresult
+             (delq nil
+                  (mapcar (lambda (n)
+                            (icalendar-get-children n name))
+                          children))))
+        (if subresult
+            (if result
+                (setq result (append result subresult))
+              (setq result subresult)))))
+    result))
+
+; private
+(defun icalendar-all-events (icalendar)
+  "Return the list of all existing events in the given ICALENDAR."
+  (interactive "")
+  (icalendar-get-children (car icalendar) 'VEVENT))
+
+(defun icalendar-split-value (value-string)
+  "Splits VALUE-STRING at ';='."
+  (let ((result '())
+        param-name param-value)
+    (when value-string
+      (save-current-buffer
+        (set-buffer (get-buffer-create " *ical-temp*"))
+        (set-buffer-modified-p nil)
+        (erase-buffer)
+        (insert value-string)
+        (goto-char (point-min))
+        (while
+           (re-search-forward
+            "\\([A-Za-z0-9-]+\\)=\\(\\([^;,:]+\\)\\|\"\\([^\"]+\\)\"\\);?"
+            nil t)
+          (setq param-name (intern (match-string 1)))
+          (setq param-value (match-string 2))
+          (setq result
+               (append result (list (list param-name param-value)))))))
+    result))
+
+(defun icalendar-decode-isodatetime (isodatetimestring)
+  "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!"
+  (icalendar-dmsg isodatetimestring)
+  (if isodatetimestring
+      ;; day/month/year must be present
+      (let ((year  (read (substring isodatetimestring 0 4)))
+            (month (read (substring isodatetimestring 4 6)))
+            (day   (read (substring isodatetimestring 6 8)))
+            (hour 0)
+            (minute 0)
+            (second 0))
+        (when (> (length isodatetimestring) 12)
+         ;; hour/minute present
+          (setq hour (read (substring isodatetimestring 9 11)))
+          (setq minute (read (substring isodatetimestring 11 13))))
+        (when (> (length isodatetimestring) 14)
+         ;; seconds present
+          (setq second (read (substring isodatetimestring 13 15))))
+        (when (and (> (length isodatetimestring) 15)
+                  ;; UTC specifier present
+                   (char-equal ?Z (aref isodatetimestring 15)))
+          ;; if not UTC add current-time-zone offset
+          (setq second (+ (car (current-time-zone)) second)))
+        ;; create the decoded date-time
+        ;; FIXME!?!
+        (condition-case nil
+            (decode-time (encode-time second minute hour day month year))
+          (error
+           (message "Cannot decode \"%s\"" isodatetimestring)
+           ;; hope for the best...
+           (list second minute hour day month year 0 nil 0))))
+    ;; isodatetimestring == nil
+    nil))
+
+(defun icalendar-decode-isoduration (isodurationstring)
+  "Return ISODURATIONSTRING in format like `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!"
+  (if isodurationstring
+      (save-match-data
+        (string-match
+         (concat
+          "^P[+-]?\\("
+          "\\(\\([0-9]+\\)D\\)"         ; days only
+          "\\|"
+          "\\(\\(\\([0-9]+\\)D\\)?T\\(\\([0-9]+\\)H\\)?" ; opt days
+          "\\(\\([0-9]+\\)M\\)?\\(\\([0-9]+\\)S\\)?\\)" ; mand. time
+          "\\|"
+          "\\(\\([0-9]+\\)W\\)"         ; weeks only
+          "\\)$") isodurationstring)
+        (let ((seconds 0)
+              (minutes 0)
+              (hours 0)
+              (days 0)
+              (months 0)
+              (years 0))
+        (cond
+         ((match-beginning 2)           ;days only
+          (setq days (read (substring isodurationstring
+                                      (match-beginning 3)
+                                      (match-end 3))))
+          (when icalendar-duration-correction
+            (setq days (1- days))))
+         ((match-beginning 4)           ;days and time
+          (if (match-beginning 5)
+              (setq days (* 7 (read (substring isodurationstring
+                                               (match-beginning 6)
+                                               (match-end 6))))))
+          (if (match-beginning 7)
+              (setq hours (read (substring isodurationstring
+                                           (match-beginning 8)
+                                           (match-end 8)))))
+          (if (match-beginning 9)
+              (setq minutes (read (substring isodurationstring
+                                             (match-beginning 10)
+                                             (match-end 10)))))
+          (if (match-beginning 11)
+              (setq seconds (read (substring isodurationstring
+                                             (match-beginning 12)
+                                             (match-end 12)))))
+          )
+         ((match-beginning 13)          ;weeks only
+          (setq days (* 7 (read (substring isodurationstring
+                                           (match-beginning 14)
+                                           (match-end 14))))))
+         )
+        (list seconds minutes hours days months years)))
+    ;; isodatetimestring == nil
+    nil))
+
+(defun icalendar-add-decoded-times (time1 time2)
+  "Add TIME1 to TIME2.
+Both times must be given in decoded form.  One of these times must be
+valid (year > 1900 or something)."
+  ;; FIXME: does this function exist already?
+  (decode-time (encode-time
+                (+ (nth 0 time1) (nth 0 time2))
+                (+ (nth 1 time1) (nth 1 time2))
+                (+ (nth 2 time1) (nth 2 time2))
+                (+ (nth 3 time1) (nth 3 time2))
+                (+ (nth 4 time1) (nth 4 time2))
+                (+ (nth 5 time1) (nth 5 time2))
+                nil
+                nil
+                ;;(or (nth 6 time1) (nth 6 time2)) ;; FIXME?
+                )))
+
+(defun icalendar-datetime-to-noneuropean-date (datetime)
+  "Convert the decoded DATETIME to non-european-style format.
+Non-European format: (month day year)."
+  (if datetime
+      (list (nth 4 datetime) ;month
+            (nth 3 datetime) ;day
+            (nth 5 datetime));year
+    ;; datetime == nil
+    nil))
+
+(defun icalendar-datetime-to-european-date (datetime)
+  "Convert the decoded DATETIME to European format.
+European format: (day month year).
+FIXME"
+  (if datetime
+      (format "%d %d %d" (nth 3 datetime); day
+              (nth 4 datetime) ;month
+              (nth 5 datetime));year
+    ;; datetime == nil
+    nil))
+
+(defun icalendar-datetime-to-colontime (datetime)
+  "Extract the time part of a decoded DATETIME into 24-hour format.
+Note that this silently ignores seconds."
+  (format "%02d:%02d" (nth 2 datetime) (nth 1 datetime)))
+
+(defun icalendar-get-month-number (monthname)
+  "Return the month number for the given MONTHNAME."
+  (save-match-data
+    (let ((case-fold-search t))
+      (assoc-default monthname icalendar-monthnumber-table
+                     'string-match))))
+
+(defun icalendar-get-weekday-abbrev (weekday)
+  "Return the abbreviated WEEKDAY."
+  ;;FIXME: ISO-like(?).
+  (save-match-data
+    (let ((case-fold-search t))
+      (assoc-default weekday icalendar-weekdayabbrev-table
+                     'string-match))))
+
+(defun icalendar-datestring-to-isodate (datestring &optional day-shift)
+  "Convert diary-style DATESTRING to iso-style date.
+If DAY-SHIFT is non-nil, the result is shifted by DAY-SHIFT days
+-- DAY-SHIFT must be either nil or an integer.  This function
+takes care of european-style."
+  (let ((day -1) month year)
+    (save-match-data
+      (cond (;; numeric date
+            (string-match (concat "\\s-*"
+                                  "0?\\([1-9][0-9]?\\)[ \t/]\\s-*"
+                                  "0?\\([1-9][0-9]?\\),?[ \t/]\\s-*"
+                                  "\\([0-9]\\{4\\}\\)")
+                          datestring)
+            (setq day (read (substring datestring (match-beginning 1)
+                                       (match-end 1))))
+            (setq month (read (substring datestring (match-beginning 2)
+                                         (match-end 2))))
+            (setq year (read (substring datestring (match-beginning 3)
+                                        (match-end 3))))
+            (unless european-calendar-style
+              (let ((x month))
+                (setq month day)
+                (setq day x))))
+           (;; date contains month names -- european-style
+            (and european-calendar-style
+                 (string-match (concat "\\s-*"
+                                       "0?\\([123]?[0-9]\\)[ \t/]\\s-*"
+                                       "\\([A-Za-z][^ ]+\\)[ \t/]\\s-*"
+                                       "\\([0-9]\\{4\\}\\)")
+                               datestring))
+                (setq day (read (substring datestring (match-beginning 1)
+                                           (match-end 1))))
+                (setq month (icalendar-get-month-number
+                             (substring datestring (match-beginning 2)
+                                        (match-end 2))))
+                (setq year (read (substring datestring (match-beginning 3)
+                                            (match-end 3)))))
+           (;; date contains month names -- non-european-style
+            (and (not european-calendar-style)
+                 (string-match (concat "\\s-*"
+                                       "\\([A-Za-z][^ ]+\\)[ \t/]\\s-*"
+                                       "0?\\([123]?[0-9]\\),?[ \t/]\\s-*"
+                                       "\\([0-9]\\{4\\}\\)")
+                               datestring))
+            (setq day (read (substring datestring (match-beginning 2)
+                                       (match-end 2))))
+            (setq month (icalendar-get-month-number
+                         (substring datestring (match-beginning 1)
+                                    (match-end 1))))
+            (setq year (read (substring datestring (match-beginning 3)
+                                        (match-end 3)))))
+           (t
+            nil)))
+    (if (> day 0)
+       (let ((mdy (calendar-gregorian-from-absolute
+                   (+ (calendar-absolute-from-gregorian (list month day year))
+                      (or day-shift 0)))))
+         (format "%04d%02d%02d" (nth 2 mdy) (nth 0 mdy) (nth 1 mdy)))
+      nil)))
+
+(defun icalendar-dmsg (&rest args)
+  "Print message ARGS if `icalendar-debug' is non-nil."
+  (if icalendar-debug
+      (apply 'message args)))
+
+(defun icalendar-diarytime-to-isotime (timestring ampmstring)
+  "Convert a a time like 9:30pm to an iso-conform string like T213000.
+In this example the TIMESTRING would be \"9:30\" and the AMPMSTRING
+would be \"pm\"."
+  (if timestring
+      (let ((starttimenum (read (icalendar-rris ":" "" timestring))))
+        ;; take care of am/pm style
+        (if (and ampmstring (string= "pm" ampmstring))
+            (setq starttimenum (+ starttimenum 1200)))
+        (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-for-import (string)
+  "Remove escape chars for comma, semicolon etc. from STRING."
+  (icalendar-rris
+   "\\\\n" "\n " (icalendar-rris
+               "\\\\\"" "\"" (icalendar-rris
+                              "\\\\;" ";" (icalendar-rris
+                                           "\\\\," "," string)))))
+
+;; ======================================================================
+;; export -- convert emacs-diary to icalendar
+;; ======================================================================
+
+(defun icalendar-convert-diary-to-ical (diary-filename ical-filename
+                                        &optional do-not-clear-diary-file)
+  "Export diary file to iCalendar format -- erases ical-filename!!!.
+Argument DIARY-FILENAME is the input `diary-file'.
+Argument ICAL-FILENAME is the output iCalendar file.
+If DO-NOT-CLEAR-DIARY-FILE is not nil the target iCalendar file
+is not erased."
+  (interactive "FExport diary data from file:
+Finto iCalendar file: ")
+  (let ((result "")
+        (start 0)
+        (entry-main "")
+        (entry-rest "")
+        (header "")
+        (contents)
+        (oops nil)
+        (nonmarker (concat "^" (regexp-quote diary-nonmarking-symbol)
+                          "?")))
+    (save-current-buffer
+      (set-buffer (find-file diary-filename))
+      (goto-char (point-min))
+      (while (re-search-forward
+              "^\\([^ \t\n].*\\)\\(\n[ \t].*\\)*" nil t)
+        (setq entry-main (match-string 1))
+        (if (match-beginning 2)
+            (setq entry-rest (match-string 2))
+          (setq entry-rest ""))
+        (setq header (format "\nBEGIN:VEVENT\nUID:emacs%d%d%d"
+                             (car (current-time))
+                             (cadr (current-time))
+                             (car (cddr (current-time)))))
+        (setq oops nil)
+        (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)
+          (setq oops t))
+         ;; float events -- FIXME
+         ((string-match
+           (concat nonmarker
+                   "%%(diary-float \\([^)]+\\))\\s-*\\(.*\\)")
+           entry-main)
+          (icalendar-dmsg "diary-float %s" entry-main)
+          (setq oops t))
+         ;; 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)
+          (setq oops t))
+         ;; 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")
+                          ":19000101" ;; FIXME? Probability that this
+                          ;; is the right day is 1/7
+                          (or starttimestring "")
+                          "\nDTEND"
+                          (if endtimestring "" ";VALUE=DATE")
+                          ":19000101" ;; FIXME?
+                          (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")
+                          (format ":1900%02d%02d" month day)
+                          (or starttimestring "")
+                          "\nDTEND"
+                          (if endtimestring "" ";VALUE=DATE")
+                          (format ":1900%02d%02d" month day)
+                          (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* ((datestring (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)))))
+            (when starttimestring
+              (unless endtimestring
+                (let ((time (read (icalendar-rris "^T0?" ""
+                                                 starttimestring))))
+                  (setq endtimestring (format "T%06d" (+ 10000 time))))))
+            (setq contents (format
+                           "\nDTSTART%s:%s%s\nDTEND%s:%s%s\nSUMMARY:%s"
+                           (if starttimestring "" ";VALUE=DATE")
+                           datestring
+                           (or starttimestring "")
+                           (if endtimestring ""
+                             ";VALUE=DATE")
+                           datestring
+                           (or endtimestring "")
+                           summary))
+            (unless (string= entry-rest "")
+              (setq contents (concat contents "\nDESCRIPTION:"
+                                     (icalendar-convert-string-for-export
+                                      entry-rest))))))
+         ;; everything else
+         (t
+          ;; Oops! what's that?
+          (setq oops t)))
+        (if oops
+            (message "Cannot export entry on line %d"
+                    (count-lines (point-min) (point)))
+          (setq result (concat result header contents "\nEND:VEVENT"))))
+      ;; we're done, insert everything into the file
+      (let ((coding-system-for-write 'utf8))
+        (set-buffer (find-file ical-filename))
+        (unless do-not-clear-diary-file
+          (erase-buffer))
+        (insert
+        "BEGIN:VCALENDAR\nPRODID:-//Emacs//NONSGML icalendar.el//EN")
+        (insert "\nVERSION:2.0")
+        (insert result)
+        (insert "\nEND:VCALENDAR\n")))))
+
+
+;; ======================================================================
+;; import -- convert icalendar to emacs-diary
+;; ======================================================================
+
+;; user function
+(defun icalendar-import-file (ical-filename diary-filename
+                                            &optional non-marking
+                                            do-not-clear-diary-file)
+  "Import a iCalendar file and save to a diary file -- erases diary-file!
+Argument ICAL-FILENAME output iCalendar file.
+Argument DIARY-FILENAME input `diary-file'.
+Optional argument NON-MARKING determines whether events are created as
+non-marking or not.
+If DO-NOT-CLEAR-DIARY-FILE is not nil the target diary file is
+not erased."
+  (interactive "fImport iCalendar data from file:
+Finto diary file (will be erased!):
+p")
+  ;; clean up the diary file
+  (save-current-buffer
+    (unless do-not-clear-diary-file
+      ;; clear the target diary file
+      (set-buffer (find-file diary-filename))
+      (erase-buffer))
+    ;; now load and convert from the ical file
+    (set-buffer (find-file ical-filename))
+    (icalendar-extract-ical-from-buffer diary-filename t non-marking)))
+
+; user function
+(defun icalendar-extract-ical-from-buffer (&optional
+                                          diary-file do-not-ask
+                                          non-marking)
+  "Extract iCalendar events from current buffer.
+
+This function searches the current buffer for the first iCalendar
+object, reads it and adds all VEVENT elements to the diary
+DIARY-FILE.
+
+It will ask for each appointment whether to add it to the diary
+when DO-NOT-ASK is non-nil.  When called interactively,
+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!"
+  (interactive)
+  (save-current-buffer
+    ;; prepare ical
+    (message "Preparing icalendar...")
+    (set-buffer (icalendar-get-unfolded-buffer (current-buffer)))
+    (goto-char (point-min))
+    (message "Preparing icalendar...done")
+    (if (re-search-forward "^BEGIN:VCALENDAR\\s-*$" nil t)
+        (let (ical-contents ical-errors)
+          ;; read ical
+          (message "Reading icalendar...")
+          (beginning-of-line)
+          (setq ical-contents (icalendar-read-element nil nil))
+          (message "Reading icalendar...done")
+          ;; convert ical
+          (message "Converting icalendar...")
+          (setq ical-errors (icalendar-convert-ical-to-diary
+                             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)))
+          (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*")))
+      (message
+       "Current buffer does not contain icalendar contents!"))))
+
+;; ----------------------------------------------------------------------
+;; private area
+;; ----------------------------------------------------------------------
+(defun icalendar-format-ical-event (event)
+  "Create a string representation of an iCalendar EVENT."
+  (let ((string icalendar-import-format)
+        (conversion-list
+         '(("%d" DESCRIPTION icalendar-import-format-description)
+           ("%s" SUMMARY     icalendar-import-format-subject)
+           ("%l" LOCATION    icalendar-import-format-location)
+           ("%o" ORGANIZER   icalendar-import-format-organizer))))
+    ;; convert the specifiers in the format string
+    (mapcar (lambda (i)
+              (let* ((spec (car i))
+                     (prop (cadr i))
+                     (format (car (cddr i)))
+                     (contents (icalendar-get-event-property event prop))
+                     (formatted-contents ""))
+                ;;(message "%s" event)
+                ;;(message "contents%s = %s" prop contents)
+                (when (and contents (> (length contents) 0))
+                  (setq formatted-contents
+                        (icalendar-rris "%s"
+                                        (icalendar-convert-for-import
+                                         contents)
+                                        (symbol-value format))))
+                (setq string (icalendar-rris spec
+                                             formatted-contents
+                                             string))))
+            conversion-list)
+    string))
+
+(defun icalendar-convert-ical-to-diary (ical-list diary-file
+                                                  &optional do-not-ask
+                                                  non-marking)
+  "Convert an iCalendar file to an Emacs diary file.
+Import VEVENTS from the iCalendar object ICAL-LIST and saves them to a
+DIARY-FILE.  If DO-NOT-ASK is nil the user is asked for each event
+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*'."
+  (let* ((ev (icalendar-all-events ical-list))
+         (error-string "")
+         (event-ok t)
+         (found-error nil)
+         e diary-string)
+    ;; step through all events/appointments
+    (while ev
+      (setq e (car ev))
+      (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)))
+                 end-d
+                 end-t
+                 (subject (icalendar-convert-for-import
+                           (or (icalendar-get-event-property e 'SUMMARY)
+                               "No Subject")))
+                 (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)
+            (when duration
+              (let ((dtend2 (icalendar-add-decoded-times
+                             dtstart
+                             (icalendar-decode-isoduration duration))))
+                (if (and dtend (not (eq dtend dtend2)))
+                    (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)
+                          start-d))
+            (setq end-t (if dtend
+                            (icalendar-datetime-to-colontime dtend)
+                          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
+                                (cdr (rassoc
+                                      byday
+                                      icalendar-weekdayabbrev-table))))
+                           (icalendar-dmsg "weekly not-all-day")
+                           (if weekday
+                               (setq diary-string
+                                     (format "%s %s%s%s" 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)))
+                ))
+             (rdate
+              (icalendar-dmsg "rdate event")
+              (setq diary-string "")
+              (mapcar (lambda (datestring)
+                        (setq diary-string
+                              (concat diary-string
+                                      (format "......"))))
+                      (icalendar-split-value rdate)))
+             ;; non-recurring event
+             ;; long 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 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 event-ok t))
+             ;; all-day event
+             (t
+              (icalendar-dmsg "all day event")
+              (setq diary-string start-d)
+              (setq event-ok t)))
+            ;; add all other elements unless the user doesn't want to have
+            ;; them
+            (if event-ok
+                (progn
+                  (setq diary-string
+                       (concat diary-string " "
+                               (icalendar-format-ical-event e)))
+                  (if do-not-ask (setq subject nil))
+                  (icalendar-add-diary-entry diary-string diary-file
+                                             non-marking subject))
+              ;; event was not ok
+              (setq found-error t)
+              (setq error-string
+                   (format "%s\nCannot handle this event:%s"
+                           error-string e))))
+        ;; 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)))))
+    (if found-error
+        (save-current-buffer
+          (set-buffer (get-buffer-create " *icalendar-errors*"))
+          (erase-buffer)
+          (insert error-string)))
+    (message "Converting icalendar...done")
+    found-error))
+
+(defun icalendar-add-diary-entry (string diary-file non-marking
+                                         &optional subject)
+  "Add STRING to the diary file DIARY-FILE.
+STRING must be a properly formatted valid diary entry.  NON-MARKING
+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)                       ;
+            (y-or-n-p (format "Add appointment for `%s' to diary? "
+                             subject)))
+    (when subject
+      (setq non-marking
+            (y-or-n-p (format "Make appointment non-marking? "))))
+    (save-window-excursion
+      (unless diary-file
+        (setq diary-file
+              (read-file-name "Add appointment to this diary file: ")))
+      (make-diary-entry string non-marking diary-file))))
+
+;; ======================================================================
+;; (add-hook 'list-diary-entries-hook 'include-icalendar-files)
+;; ======================================================================
+(defun include-icalendar-files ()
+  "Not yet implemented.")
+
+(provide 'icalendar)
+
+;;; icalendar.el ends here