]> code.delx.au - gnu-emacs/blobdiff - lisp/calendar/icalendar.el
(calendar-scroll-left): Handle case when event-start is nil.
[gnu-emacs] / lisp / calendar / icalendar.el
index 7e9e44210ef69600aa36723ff6359cd33d298e9d..1c8c83325f4745c0350fa0574bb9d0dc64103092 100644 (file)
@@ -1,6 +1,7 @@
 ;;; icalendar.el --- iCalendar implementation -*-coding: utf-8 -*-
 
-;; Copyright (C) 2002, 2003, 2004, 2005  Free Software Foundation, Inc.
+;; Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008
+;;   Free Software Foundation, Inc.
 
 ;; Author:         Ulf Jasper <ulf.jasper@web.de>
 ;; Created:        August 2002
@@ -11,7 +12,7 @@
 
 ;; 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)
+;; the Free Software Foundation; either version 3, or (at your option)
 ;; any later version.
 
 ;; GNU Emacs is distributed in the hope that it will be useful,
 
 ;;  0.07 onwards: see lisp/ChangeLog
 
-;;  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.
+;;  0.06:  (2004-10-06)
+;;  - Bugfixes regarding icalendar-import-format-*.
+;;  - Fix in icalendar-convert-diary-to-ical -- thanks to Philipp Grau.
+
+;;  0.05: (2003-06-19)
+;;  - 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: (2003-05-07)
+;;  - 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: (2003-03-21)
+;;  - First published version.  Trial version.  Alpha version.
 
 ;; ======================================================================
 ;; To Do:
 
 ;;  * Import from ical to diary:
 ;;    + Need more properties for icalendar-import-format
+;;      (added all that Mozilla Calendar uses)
+;;      From iCal specifications (RFC2445: 4.8.1), icalendar.el lacks
+;;      ATTACH, CATEGORIES, COMMENT, GEO, PERCENT-COMPLETE (VTODO),
+;;      PRIORITY, RESOURCES) not considering date/time and time-zone
 ;;    + check vcalendar version
 ;;    + check (unknown) elements
 ;;    + recurring events!
@@ -82,7 +90,7 @@
 ;;    + 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!
+;;    + timezones probably still need some improvements.
 
 ;;  * Export from diary to ical
 ;;    + diary-date, diary-float, and self-made sexp entries are not
 
 ;;; Code:
 
-(defconst icalendar-version 0.12
+(defconst icalendar-version "0.18"
   "Version number of icalendar.el.")
 
 ;; ======================================================================
 
 (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:
+  "Format for importing events from iCalendar into Emacs diary.
+It defines how iCalendar events are inserted into diary file.
+This may either be a string or a function.
+
+In case of a formatting STRING the following specifiers can be used:
+%c Class, see `icalendar-import-format-class'
 %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
+%s Summary, see `icalendar-import-format-summary'
+%t Status, see `icalendar-import-format-status'
+%u URL, see `icalendar-import-format-url'
+
+A formatting FUNCTION will be called with a VEVENT as its only
+argument.  It must return a string.  See
+`icalendar-import-format-sample' for an example."
+  :type '(choice
+          (string :tag "String")
+          (function :tag "Function"))
   :group 'icalendar)
 
-(defcustom icalendar-import-format-subject
+(defcustom icalendar-import-format-summary
   "%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."
+  "Format string defining how the summary element is formatted.
+This applies only if the summary is not empty! `%s' is replaced
+by the summary."
   :type 'string
   :group 'icalendar)
 
@@ -152,6 +171,48 @@ replaced by the organizer."
   :type 'string
   :group 'icalendar)
 
+(defcustom icalendar-import-format-url
+  "\n URL: %s"
+  "Format string defining how the URL element is formatted.
+This applies only if the URL is not empty! `%s' is replaced by
+the URL."
+  :type 'string
+  :group 'icalendar)
+
+(defcustom icalendar-import-format-status
+  "\n Status: %s"
+  "Format string defining how the status element is formatted.
+This applies only if the status is not empty! `%s' is replaced by
+the status."
+  :type 'string
+  :group 'icalendar)
+
+(defcustom icalendar-import-format-class
+  "\n Class: %s"
+  "Format string defining how the class element is formatted.
+This applies only if the class is not empty! `%s' is replaced by
+the class."
+  :type 'string
+  :group 'icalendar)
+
+(defcustom icalendar-recurring-start-year
+  2005
+  "Start year for recurring events.
+Some calendar browsers only propagate recurring events for
+several years beyond the start time.  Set this string to a year
+just before the start of your personal calendar."
+  :type 'integer
+  :group 'icalendar)
+
+
+(defcustom icalendar-export-hidden-diary-entries
+  t
+  "Determines whether hidden diary entries are exported.
+If non-nil hidden diary entries (starting with `&') get exported,
+if nil they are ignored."
+  :type 'boolean
+  :group 'icalendar)
+
 (defvar icalendar-debug nil
   "Enable icalendar debug messages.")
 
@@ -195,22 +256,24 @@ buffer."
         (replace-match "" nil nil)))
     unfolded-buffer))
 
-(defsubst icalendar--rris (re rp st)
-  "Replace regexp RE with RP in string ST and return the new string.
-This is here for compatibility with XEmacs."
-  ;; 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)))
+(defsubst icalendar--rris (regexp rep string &optional fixedcase literal)
+  "Replace regular expression in string.
+Pass arguments REGEXP REP STRING FIXEDCASE LITERAL to
+`replace-regexp-in-string' (Emacs) or to `replace-in-string' (XEmacs)."
+  (cond ((fboundp 'replace-regexp-in-string)
+         ;; Emacs:
+         (replace-regexp-in-string regexp rep string fixedcase literal))
+        ((fboundp 'replace-in-string)
+         ;; XEmacs:
+         (save-match-data ;; apparently XEmacs needs save-match-data
+           (replace-in-string string regexp rep literal)))))
 
 (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"
+it finds."
   (let (element children line name params param param-name param-value
                 value
                 (continue t))
@@ -265,7 +328,7 @@ it finds"
 ;;  (car (cddr event)))
 
 (defun icalendar--get-event-property (event prop)
-  "For the given EVENT return the value of the first occurence of PROP."
+  "For the given EVENT return the value of the first occurrence of PROP."
   (catch 'found
     (let ((props (car (cddr event))) pp)
       (while props
@@ -276,7 +339,7 @@ it finds"
     nil))
 
 (defun icalendar--get-event-property-attributes (event prop)
-  "For the given EVENT return attributes of the first occurence of PROP."
+  "For the given EVENT return attributes of the first occurrence of PROP."
   (catch 'found
     (let ((props (car (cddr event))) pp)
       (while props
@@ -357,15 +420,90 @@ children."
                 (append result (list (list param-name param-value)))))))
     result))
 
-(defun icalendar--decode-isodatetime (isodatetimestring &optional day-shift)
+(defun icalendar--convert-tz-offset (alist dst-p)
+  "Return a cons of two strings representing a timezone start.
+ALIST is an alist entry from a VTIMEZONE, like STANDARD.
+DST-P is non-nil if this is for daylight savings time.
+The strings are suitable for assembling into a TZ variable."
+  (let ((offset (car (cddr (assq 'TZOFFSETTO alist))))
+       (rrule-value (car (cddr (assq 'RRULE alist))))
+       (dtstart (car (cddr (assq 'DTSTART alist)))))
+    ;; FIXME: for now we only handle RRULE and not RDATE here.
+    (when (and offset rrule-value dtstart)
+      (let* ((rrule (icalendar--split-value rrule-value))
+            (freq (cadr (assq 'FREQ rrule)))
+            (bymonth (cadr (assq 'BYMONTH rrule)))
+            (byday (cadr (assq 'BYDAY rrule))))
+       ;; FIXME: we don't correctly handle WKST here.
+       (if (and (string= freq "YEARLY") bymonth)
+           (cons
+            (concat
+             ;; Fake a name.
+             (if dst-p "(DST?)" "(STD?)")
+             ;; For TZ, OFFSET is added to the local time.  So,
+             ;; invert the values.
+             (if (eq (aref offset 0) ?-) "+" "-")
+             (substring offset 1 3)
+             ":"
+             (substring offset 3 5))
+            ;; The start time.
+            (let* ((day (icalendar--get-weekday-number (substring byday -2)))
+                   (week (if (eq day -1)
+                             byday
+                           (substring byday 0 -2))))
+              (concat "M" bymonth "." week "." (if (eq day -1) "0"
+                                                 (int-to-string day))
+                      ;; Start time.
+                      "/"
+                      (substring dtstart -6 -4)
+                      ":"
+                      (substring dtstart -4 -2)
+                      ":"
+                      (substring dtstart -2)))))))))
+
+(defun icalendar--parse-vtimezone (alist)
+  "Turn a VTIMEZONE ALIST into a cons (ID . TZ-STRING).
+Return nil if timezone cannot be parsed."
+  (let* ((tz-id (icalendar--get-event-property alist 'TZID))
+        (daylight (cadr (cdar (icalendar--get-children alist 'DAYLIGHT))))
+        (day (and daylight (icalendar--convert-tz-offset daylight t)))
+        (standard (cadr (cdar (icalendar--get-children alist 'STANDARD))))
+        (std (and standard (icalendar--convert-tz-offset standard nil))))
+    (if (and tz-id std)
+       (cons tz-id
+             (if day
+                 (concat (car std) (car day)
+                         "," (cdr day) "," (cdr std))
+               (car std))))))
+
+(defun icalendar--convert-all-timezones (icalendar)
+  "Convert all timezones in the ICALENDAR into an alist.
+Each element of the alist is a cons (ID . TZ-STRING),
+like `icalendar--parse-vtimezone'."
+  (let (result)
+    (dolist (zone (icalendar--get-children (car icalendar) 'VTIMEZONE))
+      (setq zone (icalendar--parse-vtimezone zone))
+      (if zone
+         (setq result (cons zone result))))
+    result))
+
+(defun icalendar--find-time-zone (prop-list zone-map)
+  "Return a timezone string for the time zone in PROP-LIST, or nil if none.
+ZONE-MAP is a timezone alist as returned by `icalendar--convert-all-timezones'."
+  (let ((id (plist-get prop-list 'TZID)))
+    (if id
+       (cdr (assoc id zone-map)))))
+
+(defun icalendar--decode-isodatetime (isodatetimestring &optional day-shift
+                                                        zone)
   "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!  If optional
 parameter DAY-SHIFT is non-nil the result is shifted by DAY-SHIFT
 days.
+ZONE, if provided, is the timezone, in any format understood by `encode-time'.
 
-FIXME: TZID-attributes are ignored....!
 FIXME: multiple comma-separated values should be allowed!"
   (icalendar--dmsg isodatetimestring)
   (if isodatetimestring
@@ -400,7 +538,7 @@ FIXME: multiple comma-separated values should be allowed!"
         ;; create the decoded date-time
         ;; FIXME!?!
         (condition-case nil
-            (decode-time (encode-time second minute hour day month year))
+            (decode-time (encode-time second minute hour day month year zone))
           (error
            (message "Cannot decode \"%s\"" isodatetimestring)
            ;; hope for the best...
@@ -486,11 +624,11 @@ valid (year > 1900 or something)."
                 ;;(or (nth 6 time1) (nth 6 time2)) ;; FIXME?
                 )))
 
-(defun icalendar--datetime-to-noneuropean-date (datetime &optional separator)
-  "Convert the decoded DATETIME to non-european-style format.
+(defun icalendar--datetime-to-american-date (datetime &optional separator)
+  "Convert the decoded DATETIME to American-style format.
 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\"."
+American format: \"month day year\"."
   (if datetime
       (format "%d%s%d%s%d" (nth 4 datetime) ;month
               (or separator " ")
@@ -500,6 +638,9 @@ Non-European format: \"month day year\"."
     ;; datetime == nil
     nil))
 
+(define-obsolete-function-alias 'icalendar--datetime-to-noneuropean-date
+  'icalendar--datetime-to-american-date "icalendar 0.19")
+
 (defun icalendar--datetime-to-european-date (datetime &optional separator)
   "Convert the decoded DATETIME to European format.
 Optional argument SEPARATOR gives the separator between month,
@@ -515,15 +656,33 @@ FIXME"
     ;; datetime == nil
     nil))
 
+(defun icalendar--datetime-to-iso-date (datetime &optional separator)
+  "Convert the decoded DATETIME to ISO format.
+Optional argument SEPARATOR gives the separator between month,
+day, and year.  If nil a blank character is used as separator.
+ISO format: (year month day)."
+  (if datetime
+      (format "%d%s%d%s%d" (nth 5 datetime) ;year
+              (or separator " ")
+              (nth 4 datetime)            ;month
+              (or separator " ")
+              (nth 3 datetime))           ;day
+    ;; 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)))
+Call icalendar--datetime-to-*-date according to the
+value of `calendar-date-style' (or the older `european-calendar-style')."
+  (funcall (intern-soft (format "icalendar--datetime-to-%s-date"
+                                (if (boundp 'calendar-date-style)
+                                    calendar-date-style
+                                  (if (with-no-warnings european-calendar-style)
+                                      'european
+                                    'american))))
+           datetime separator))
 
 (defun icalendar--datetime-to-colontime (datetime)
   "Extract the time part of a decoded DATETIME into 24-hour format.
@@ -609,12 +768,11 @@ takes care of european-style."
                  (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))
+             (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
@@ -623,12 +781,11 @@ takes care of european-style."
              (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))
+             (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
@@ -647,13 +804,14 @@ takes care of european-style."
       nil)))
 
 (defun icalendar--diarytime-to-isotime (timestring ampmstring)
-  "Convert a time like 9:30pm to an iso-conform string like T213000.
+  "Convert 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))
+        ;; Be sure *not* to convert 12:00pm - 12:59pm to 2400-2459
+        (if (and ampmstring (string= "pm" ampmstring) (< starttimenum 1200))
             (setq starttimenum (+ starttimenum 1200)))
         (format "T%04d00" starttimenum))
     nil))
@@ -704,10 +862,12 @@ FExport diary data into iCalendar file: ")
         (entry-main "")
         (entry-rest "")
         (header "")
+        (contents-n-summary)
         (contents)
         (found-error nil)
         (nonmarker (concat "^" (regexp-quote diary-nonmarking-symbol)
-                           "?")))
+                           "?"))
+        (other-elements nil))
     ;; prepare buffer with error messages
     (save-current-buffer
       (set-buffer (get-buffer-create "*icalendar-errors*"))
@@ -717,7 +877,10 @@ FExport diary data into iCalendar file: ")
     (save-excursion
       (goto-char min)
       (while (re-search-forward
-              "^\\([^ \t\n].+\\)\\(\\(\n[ \t].*\\)*\\)" max t)
+              ;; possibly ignore hidden entries beginning with "&"
+              (if icalendar-export-hidden-diary-entries
+                  "^\\([^ \t\n#].+\\)\\(\\(\n[ \t].*\\)*\\)"
+                "^\\([^ \t\n&#].+\\)\\(\\(\n[ \t].*\\)*\\)") max t)
         (setq entry-main (match-string 1))
         (if (match-beginning 2)
             (setq entry-rest (match-string 2))
@@ -728,36 +891,33 @@ FExport diary data into iCalendar file: ")
                              (car (cddr (current-time)))))
         (condition-case error-val
             (progn
-              (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 contents-n-summary
+                    (icalendar--convert-to-ical nonmarker entry-main))
+              (setq other-elements (icalendar--parse-summary-and-rest
+                                    (concat entry-main entry-rest)))
+              (setq contents (concat (car contents-n-summary)
+                                     "\nSUMMARY:" (cadr contents-n-summary)))
+              (let ((cla (cdr (assoc 'cla other-elements)))
+                    (des (cdr (assoc 'des other-elements)))
+                    (loc (cdr (assoc 'loc other-elements)))
+                    (org (cdr (assoc 'org other-elements)))
+                    (sta (cdr (assoc 'sta other-elements)))
+                    (sum (cdr (assoc 'sum other-elements)))
+                    (url (cdr (assoc 'url other-elements))))
+                (if cla
+                    (setq contents (concat contents "\nCLASS:" cla)))
+                (if des
+                    (setq contents (concat contents "\nDESCRIPTION:" des)))
+                (if loc
+                    (setq contents (concat contents "\nLOCATION:" loc)))
+                (if org
+                    (setq contents (concat contents "\nORGANIZER:" org)))
+                (if sta
+                    (setq contents (concat contents "\nSTATUS:" sta)))
+                ;;(if sum
+                ;;    (setq contents (concat contents "\nSUMMARY:" sum)))
+                (if url
+                    (setq contents (concat contents "\nURL:" url))))
               (setq result (concat result header contents "\nEND:VEVENT")))
           ;; handle errors
           (error
@@ -766,7 +926,7 @@ FExport diary data into iCalendar file: ")
              (set-buffer (get-buffer-create "*icalendar-errors*"))
              (insert (format "Error in line %d -- %s: `%s'\n"
                              (count-lines (point-min) (point))
-                             (cadr error-val)
+                             error-val
                              entry-main))))))
 
       ;; we're done, insert everything into the file
@@ -780,13 +940,133 @@ FExport diary data into iCalendar file: ")
           (insert result)
           (insert "\nEND:VCALENDAR\n")
           ;; save the diary file
-          (save-buffer))))
+          (save-buffer)
+          (unless found-error
+            (bury-buffer)))))
     found-error))
 
-;; subroutines
+(defun icalendar--convert-to-ical (nonmarker entry-main)
+  "Convert a 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."
+  (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")))
+
+(defun icalendar--parse-summary-and-rest (summary-and-rest)
+  "Parse SUMMARY-AND-REST from a diary to fill iCalendar properties.
+Returns an alist."
+  (save-match-data
+    (if (functionp icalendar-import-format)
+        ;; can't do anything
+        nil
+      ;; split summary-and-rest
+      (let* ((s icalendar-import-format)
+             (p-cla (or (string-match "%c" icalendar-import-format) -1))
+             (p-des (or (string-match "%d" icalendar-import-format) -1))
+             (p-loc (or (string-match "%l" icalendar-import-format) -1))
+             (p-org (or (string-match "%o" icalendar-import-format) -1))
+             (p-sum (or (string-match "%s" icalendar-import-format) -1))
+             (p-sta (or (string-match "%t" icalendar-import-format) -1))
+             (p-url (or (string-match "%u" icalendar-import-format) -1))
+             (p-list (sort (list p-cla p-des p-loc p-org p-sta p-sum p-url) '<))
+             pos-cla pos-des pos-loc pos-org pos-sta pos-sum pos-url)
+        (dotimes (i (length p-list))
+          (cond ((and (>= p-cla 0) (= (nth i p-list) p-cla))
+                 (setq pos-cla (+ 2 (* 2 i))))
+                ((and (>= p-des 0) (= (nth i p-list) p-des))
+                 (setq pos-des (+ 2 (* 2 i))))
+                ((and (>= p-loc 0) (= (nth i p-list) p-loc))
+                 (setq pos-loc (+ 2 (* 2 i))))
+                ((and (>= p-org 0) (= (nth i p-list) p-org))
+                 (setq pos-org (+ 2 (* 2 i))))
+                ((and (>= p-sta 0) (= (nth i p-list) p-sta))
+                 (setq pos-sta (+ 2 (* 2 i))))
+                ((and (>= p-sum 0) (= (nth i p-list) p-sum))
+                 (setq pos-sum (+ 2 (* 2 i))))
+                ((and (>= p-url 0) (= (nth i p-list) p-url))
+                 (setq pos-url (+ 2 (* 2 i))))))
+        (mapc (lambda (ij)
+                (setq s (icalendar--rris (car ij) (cadr ij) s t t)))
+              (list
+               ;; summary must be first! because of %s
+               (list "%s"
+                     (concat "\\(" icalendar-import-format-summary "\\)??"))
+               (list "%c"
+                     (concat "\\(" icalendar-import-format-class "\\)??"))
+               (list "%d"
+                     (concat "\\(" icalendar-import-format-description "\\)??"))
+               (list "%l"
+                     (concat "\\(" icalendar-import-format-location "\\)??"))
+               (list "%o"
+                     (concat "\\(" icalendar-import-format-organizer "\\)??"))
+               (list "%t"
+                     (concat "\\(" icalendar-import-format-status "\\)??"))
+               (list "%u"
+                     (concat "\\(" icalendar-import-format-url "\\)??"))))
+        (setq s (concat "^" (icalendar--rris "%s" "\\(.*?\\)" s nil t)
+                        " $"))
+        (if (string-match s summary-and-rest)
+            (let (cla des loc org sta sum url)
+              (if (and pos-sum (match-beginning pos-sum))
+                  (setq sum (substring summary-and-rest
+                                       (match-beginning pos-sum)
+                                       (match-end pos-sum))))
+              (if (and pos-cla (match-beginning pos-cla))
+                  (setq cla (substring summary-and-rest
+                                       (match-beginning pos-cla)
+                                       (match-end pos-cla))))
+              (if (and pos-des (match-beginning pos-des))
+                  (setq des (substring summary-and-rest
+                                       (match-beginning pos-des)
+                                       (match-end pos-des))))
+              (if (and pos-loc (match-beginning pos-loc))
+                  (setq loc (substring summary-and-rest
+                                       (match-beginning pos-loc)
+                                       (match-end pos-loc))))
+              (if (and pos-org (match-beginning pos-org))
+                  (setq org (substring summary-and-rest
+                                       (match-beginning pos-org)
+                                       (match-end pos-org))))
+              (if (and pos-sta (match-beginning pos-sta))
+                  (setq sta (substring summary-and-rest
+                                       (match-beginning pos-sta)
+                                       (match-end pos-sta))))
+              (if (and pos-url (match-beginning pos-url))
+                  (setq url (substring summary-and-rest
+                                       (match-beginning pos-url)
+                                       (match-end pos-url))))
+              (list (if cla (cons 'cla cla) nil)
+                    (if des (cons 'des des) nil)
+                    (if loc (cons 'loc loc) nil)
+                    (if org (cons 'org org) nil)
+                    (if sta (cons 'sta sta) nil)
+                    ;;(if sum (cons 'sum sum) nil)
+                    (if url (cons 'url url) nil))))))))
+
+;; subroutines for icalendar-export-region
 (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
@@ -795,7 +1075,7 @@ entries.  ENTRY-MAIN is the first line of the diary entry."
                             "\\("
                             "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
                             "\\)?"
-                            "\\s-*\\(.*\\)")
+                            "\\s-*\\(.*?\\) ?$")
                     entry-main)
       (let* ((datetime (substring entry-main (match-beginning 1)
                                   (match-end 1)))
@@ -803,6 +1083,7 @@ entries.  ENTRY-MAIN is the first line of the diary entry."
                               datetime))
              (endisostring (icalendar--datestring-to-isodate
                             datetime 1))
+             (endisostring1)
              (starttimestring (icalendar--diarytime-to-isotime
                                (if (match-beginning 3)
                                    (substring entry-main
@@ -832,33 +1113,56 @@ entries.  ENTRY-MAIN is the first line of the diary entry."
 
         (unless startisostring
           (error "Could not parse date"))
+
+        ;; If only start-date is specified, then end-date is next day,
+        ;; otherwise it is same day.
+        (setq endisostring1 (if starttimestring
+                                startisostring
+                              endisostring))
+
         (when starttimestring
           (unless endtimestring
             (let ((time
                    (read (icalendar--rris "^T0?" ""
                                           starttimestring))))
+              (if (< time 230000)
+                  ;; Case: ends on same day
               (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))
+                                              (+ 10000 time)))
+                ;; Case: ends on next day
+                (setq endtimestring (format "T%06d"
+                                              (- time 230000)))
+                (setq endisostring1 endisostring)) )))
+
+        (list (concat "\nDTSTART;"
+                      (if starttimestring "VALUE=DATE-TIME:"
+                        "VALUE=DATE:")
+                      startisostring
+                      (or starttimestring "")
+                      "\nDTEND;"
+                      (if endtimestring "VALUE=DATE-TIME:"
+                        "VALUE=DATE:")
+                      endisostring1
+                      (or endtimestring ""))
+              summary))
     ;; no match
     nil))
 
+(defun icalendar-first-weekday-of-year (abbrevweekday year)
+  "Find the first ABBREVWEEKDAY in a given YEAR.
+Returns day number."
+  (let* ((day-of-week-jan01 (calendar-day-of-week (list 1 1 year)))
+         (result (+ 1
+                    (- (icalendar--get-weekday-number abbrevweekday)
+                       day-of-week-jan01))))
+    (cond ((<= result 0)
+           (setq result (+ result 7)))
+          ((> result 7)
+           (setq result (- result 7))))
+    result))
+
 (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
@@ -869,7 +1173,7 @@ entries.  ENTRY-MAIN is the first line of the diary entry."
                                  "\\([1-9][0-9]?:[0-9][0-9]\\)"
                                  "\\([ap]m\\)?\\)?"
                                  "\\)?"
-                                 "\\s-*\\(.*\\)$")
+                                 "\\s-*\\(.*?\\) ?$")
                          entry-main)
            (icalendar--get-weekday-abbrev
             (substring entry-main (match-beginning 1)
@@ -911,35 +1215,36 @@ entries.  ENTRY-MAIN is the first line of the diary entry."
                                           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))
+        (list (concat "\nDTSTART;"
+                      (if starttimestring
+                          "VALUE=DATE-TIME:"
+                        "VALUE=DATE:")
+                      ;; Find the first requested weekday of the
+                      ;; start year
+                      (funcall 'format "%04d%02d%02d"
+                               icalendar-recurring-start-year 1
+                               (icalendar-first-weekday-of-year
+                                day icalendar-recurring-start-year))
+                      (or starttimestring "")
+                      "\nDTEND;"
+                      (if endtimestring
+                          "VALUE=DATE-TIME:"
+                        "VALUE=DATE:")
+                      (funcall 'format "%04d%02d%02d"
+                       ;; end is non-inclusive!
+                               icalendar-recurring-start-year 1
+                               (+ (icalendar-first-weekday-of-year
+                                   day icalendar-recurring-start-year)
+                          (if endtimestring 0 1)))
+                      (or endtimestring "")
+                      "\nRRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY="
+                      day)
+              summary))
     ;; 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
@@ -951,7 +1256,7 @@ entries.  ENTRY-MAIN is the first line of the diary entry."
                             "\\("
                             "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
                             "\\)?"
-                            "\\s-*\\([^0-9]+.*\\)$" ; must not match years
+                            "\\s-*\\([^0-9]+.*?\\) ?$" ; must not match years
                             )
                     entry-main)
       (let* ((daypos (if european-calendar-style 1 2))
@@ -997,25 +1302,24 @@ entries.  ENTRY-MAIN is the first line of the diary entry."
                                           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)))
+        (list (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 "")
+                      "\nRRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH="
+                      (format "%d" month)
+                      ";BYMONTHDAY="
+                      (format "%d" day))
+              summary))
     ;; no match
     nil))
 
@@ -1026,18 +1330,28 @@ 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))
+  (cond ((string-match (concat nonmarker
+                               "%%(and \\(([^)]+)\\))\\(\\s-*.*?\\) ?$")
+                       entry-main)
+         ;; simple sexp entry as generated by icalendar.el: strip off the
+         ;; unnecessary (and)
+         (icalendar--dmsg "diary-sexp from icalendar.el %s" entry-main)
+         (icalendar--convert-to-ical
+          nonmarker
+          (concat "%%"
+                  (substring entry-main (match-beginning 1) (match-end 1))
+                  (substring entry-main (match-beginning 2) (match-end 2)))))
+        ((string-match (concat nonmarker
+                               "%%([^)]+)\\s-*.*")
+                       entry-main)
+         (icalendar--dmsg "diary-sexp %s" entry-main)
+         (error "Sexp-entries are not supported yet"))
+        (t
+         ;; 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
@@ -1047,7 +1361,7 @@ entries.  ENTRY-MAIN is the first line of the diary entry."
                             "\\("
                             "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
                             "\\)?"
-                            "\\s-*\\(.*\\)")
+                            "\\s-*\\(.*?\\) ?$")
                     entry-main)
       (let* ((startstring (substring entry-main
                                      (match-beginning 1)
@@ -1096,20 +1410,19 @@ entries.  ENTRY-MAIN is the first line of the diary entry."
                                           (+ 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)
+            (list (concat "\nDTSTART;VALUE=DATE-TIME:"
+                          startisostring
+                          starttimestring
+                          "\nDTEND;VALUE=DATE-TIME:"
+                          startisostring
+                          endtimestring
+                          "\nRRULE:FREQ=DAILY;INTERVAL=1;UNTIL="
+                          endisostring)
+                  summary)
           ;; no time -> write long event
-          (concat "\nDTSTART;VALUE=DATE:" startisostring
-                  "\nDTEND;VALUE=DATE:" endisostring+1
-                  "\nSUMMARY:" summary)))
+          (list (concat "\nDTSTART;VALUE=DATE:" startisostring
+                        "\nDTEND;VALUE=DATE:" endisostring+1)
+                summary)))
     ;; no match
     nil))
 
@@ -1121,7 +1434,7 @@ 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-*\\(.*\\)")
+                            "%%(diary-float \\([^)]+\\))\\s-*\\(.*?\\) ?$")
                     entry-main)
       (progn
         (icalendar--dmsg "diary-float %s" entry-main)
@@ -1137,7 +1450,7 @@ 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-*\\(.*\\)")
+                            "%%(diary-date \\([^)]+\\))\\s-*\\(.*?\\) ?$")
                     entry-main)
       (progn
         (icalendar--dmsg "diary-date %s" entry-main)
@@ -1147,7 +1460,6 @@ entries.  ENTRY-MAIN is the first line of the diary entry."
 
 (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
@@ -1157,7 +1469,7 @@ entries.  ENTRY-MAIN is the first line of the diary entry."
                             "\\("
                             "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
                             "\\)?"
-                            "\\s-*\\(.*\\)")
+                            "\\s-*\\(.*?\\) ?$")
                     entry-main)
       (let* ((frequency (substring entry-main (match-beginning 1)
                                    (match-end 1)))
@@ -1202,27 +1514,26 @@ entries.  ENTRY-MAIN is the first line of the diary entry."
                                           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...
-                ))
+        (list (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 "")
+                      "\nRRULE:FREQ=DAILY;INTERVAL=" frequency
+                      ;; strange: korganizer does not expect
+                      ;; BYSOMETHING here...
+                      )
+              summary))
     ;; 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
@@ -1231,7 +1542,7 @@ entries.  ENTRY-MAIN is the first line of the diary entry."
                             "\\("
                             "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
                             "\\)?"
-                            "\\s-*\\(.*\\)")
+                            "\\s-*\\(.*?\\) ?$")
                     entry-main)
       (let* ((datetime (substring entry-main (match-beginning 1)
                                   (match-end 1)))
@@ -1272,26 +1583,26 @@ entries.  ENTRY-MAIN is the first line of the diary entry."
                                           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)))
+        (list (concat "\nDTSTART;"
+                      (if starttimestring "VALUE=DATE-TIME:"
+                        "VALUE=DATE:")
+                      startisostring
+                      (or starttimestring "")
+                      "\nDTEND;"
+                      (if endtimestring "VALUE=DATE-TIME:"
+                        "VALUE=DATE:")
+                      endisostring
+                      (or endtimestring "")
+                      "\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))
+              summary))
     ;; no match
     nil))
 
@@ -1302,7 +1613,7 @@ entries.  ENTRY-MAIN is the first line of the diary entry."
 ;;;###autoload
 (defun icalendar-import-file (ical-filename diary-filename
                                             &optional non-marking)
-  "Import a iCalendar file and append to a diary file.
+  "Import an iCalendar file and append to a diary file.
 Argument ICAL-FILENAME output iCalendar file.
 Argument DIARY-FILENAME input `diary-file'.
 Optional argument NON-MARKING determines whether events are created as
@@ -1326,14 +1637,14 @@ 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.
+unless DO-NOT-ASK is non-nil.  When called interactively,
+DO-NOT-ASK is nil, so that you are asked for each event.
 
 NON-MARKING determines whether diary events are created as
 non-marking.
 
 Return code t means that importing worked well, return code nil
-means that an error has occured.  Error messages will be in the
+means that an error has occurred.  Error messages will be in the
 buffer `*icalendar-errors*'."
   (interactive)
   (save-current-buffer
@@ -1362,7 +1673,7 @@ buffer `*icalendar-errors*'."
                   (set-buffer b)
                   (save-buffer)))))
           (message "Converting icalendar...done")
-          ;; return t if no error occured
+          ;; return t if no error occurred
           (not ical-errors))
       (message
        "Current buffer does not contain icalendar contents!")
@@ -1374,35 +1685,42 @@ buffer `*icalendar-errors*'."
 
 (defun icalendar--format-ical-event (event)
   "Create a string representation of an iCalendar EVENT."
-  (let ((string icalendar-import-format)
+  (if (functionp icalendar-import-format)
+      (funcall icalendar-import-format event)
+    (let ((string icalendar-import-format)
         (conversion-list
-         '(("%d" DESCRIPTION icalendar-import-format-description)
-           ("%s" SUMMARY     icalendar-import-format-subject)
+         '(("%c" CLASS       icalendar-import-format-class)
+           ("%d" DESCRIPTION icalendar-import-format-description)
            ("%l" LOCATION    icalendar-import-format-location)
-           ("%o" ORGANIZER   icalendar-import-format-organizer))))
+           ("%o" ORGANIZER   icalendar-import-format-organizer)
+           ("%s" SUMMARY     icalendar-import-format-summary)
+           ("%t" STATUS      icalendar-import-format-status)
+           ("%u" URL         icalendar-import-format-url))))
     ;; 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 ""))
-                (when (and contents (> (length contents) 0))
-                  (setq formatted-contents
-                        (icalendar--rris "%s"
-                                         (icalendar--convert-string-for-import
-                                          contents)
-                                         (symbol-value format))))
-                (setq string (icalendar--rris spec
-                                              formatted-contents
-                                              string))))
-            conversion-list)
-    string))
+    (mapc (lambda (i)
+           (let* ((spec (car i))
+                  (prop (cadr i))
+                  (format (car (cddr i)))
+                  (contents (icalendar--get-event-property event prop))
+                  (formatted-contents ""))
+             (when (and contents (> (length contents) 0))
+               (setq formatted-contents
+                     (icalendar--rris "%s"
+                                      (icalendar--convert-string-for-import
+                                       contents)
+                                      (symbol-value format)
+                                      t t)))
+             (setq string (icalendar--rris spec
+                                           formatted-contents
+                                           string
+                                           t t))))
+         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.
+  "Convert iCalendar data 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
@@ -1414,6 +1732,7 @@ written into the buffer `*icalendar-errors*'."
          (error-string "")
          (event-ok t)
          (found-error nil)
+         (zone-map (icalendar--convert-all-timezones ical-list))
          e diary-string)
     ;; step through all events/appointments
     (while ev
@@ -1422,23 +1741,34 @@ written into the buffer `*icalendar-errors*'."
       (setq event-ok nil)
       (condition-case error-val
           (let* ((dtstart (icalendar--get-event-property e 'DTSTART))
-                 (dtstart-dec (icalendar--decode-isodatetime dtstart))
+                 (dtstart-zone (icalendar--find-time-zone
+                               (icalendar--get-event-property-attributes
+                                e 'DTSTART)
+                               zone-map))
+                 (dtstart-dec (icalendar--decode-isodatetime dtstart nil
+                                                             dtstart-zone))
                  (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))
+                 (dtend-zone (icalendar--find-time-zone
+                             (icalendar--get-event-property-attributes
+                              e 'DTEND)
+                             zone-map))
+                 (dtend-dec (icalendar--decode-isodatetime dtend
+                                                           nil dtend-zone))
+                 (dtend-1-dec (icalendar--decode-isodatetime dtend -1
+                                                             dtend-zone))
                  end-d
                  end-1-d
                  end-t
-                 (subject (icalendar--convert-string-for-import
+                 (summary (icalendar--convert-string-for-import
                            (or (icalendar--get-event-property e 'SUMMARY)
-                               "No Subject")))
+                               "No summary")))
                  (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 summary)
             ;; check whether start-time is missing
             (if  (and dtstart
                       (string=
@@ -1456,7 +1786,7 @@ written into the buffer `*icalendar-errors*'."
                                                                    t))))
                 (if (and dtend-dec (not (eq dtend-dec dtend-dec-d)))
                     (message "Inconsistent endtime and duration for %s"
-                             subject))
+                             summary))
                 (setq dtend-dec dtend-dec-d)
                 (setq dtend-1-dec dtend-1-dec-d)))
             (setq end-d (if dtend-dec
@@ -1485,11 +1815,11 @@ written into the buffer `*icalendar-errors*'."
              (rdate
               (icalendar--dmsg "rdate event")
               (setq diary-string "")
-              (mapcar (lambda (datestring)
-                        (setq diary-string
-                              (concat diary-string
-                                      (format "......"))))
-                      (icalendar--split-value rdate)))
+              (mapc (lambda (datestring)
+                     (setq diary-string
+                           (concat diary-string
+                                   (format "......"))))
+                   (icalendar--split-value rdate)))
              ;; non-recurring event
              ;; all-day event
              ((not (string= start-d end-d))
@@ -1517,9 +1847,12 @@ written into the buffer `*icalendar-errors*'."
                   (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))
+                  (if do-not-ask (setq summary nil))
+                  ;; add entry to diary and store actual name of diary
+                  ;; file (in case it was nil)
+                  (setq diary-file
+                        (icalendar--add-diary-entry diary-string diary-file
+                                                    non-marking summary)))
               ;; event was not ok
               (setq found-error t)
               (setq error-string
@@ -1532,7 +1865,16 @@ written into the buffer `*icalendar-errors*'."
          (setq found-error t)
          (setq error-string (format "%s\n%s\nCannot handle this event: %s"
                                     error-val error-string e))
-         (message error-string))))
+         (message "%s" error-string))))
+
+    ;; insert final newline
+    (if diary-file
+        (let ((b (find-buffer-visiting diary-file)))
+          (when b
+            (save-current-buffer
+              (set-buffer b)
+              (goto-char (point-max))
+              (insert "\n")))))
     (if found-error
         (save-current-buffer
           (set-buffer (get-buffer-create "*icalendar-errors*"))
@@ -1570,7 +1912,7 @@ END-T is the event's end time in diary format."
         (let ((until-1 0))
           (cond ((string-equal frequency "DAILY")
                  (setq until (icalendar--add-decoded-times
-                              dtstart-dec 
+                              dtstart-dec
                               (list 0 0 0 (* (read count) interval) 0 0)))
                  (setq until-1 (icalendar--add-decoded-times
                                 dtstart-dec
@@ -1767,24 +2109,45 @@ END-T is the event's end time in diary format."
                  start-t))))
 
 (defun icalendar--add-diary-entry (string diary-file non-marking
-                                          &optional subject)
+                                          &optional summary)
   "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
+SUMMARY is not nil it must be a string that gives the summary 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 summary)
             (y-or-n-p (format "Add appointment for `%s' to diary? "
-                              subject)))
-    (when subject
+                              summary)))
+    (when summary
       (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))))
+      ;; Note: diary-make-entry will add a trailing blank char.... :(
+      (funcall (if (fboundp 'diary-make-entry)
+                   'diary-make-entry
+                 'make-diary-entry)
+               string non-marking diary-file)))
+  ;; return diary-file in case it has been changed interactively
+  diary-file)
+
+;; ======================================================================
+;; Examples
+;; ======================================================================
+(defun icalendar-import-format-sample (event)
+  "Example function for formatting an icalendar EVENT."
+  (format (concat "SUMMARY=`%s' DESCRIPTION=`%s' LOCATION=`%s' ORGANIZER=`%s' "
+                  "STATUS=`%s' URL=`%s' CLASS=`%s'")
+          (or (icalendar--get-event-property event 'SUMMARY) "")
+          (or (icalendar--get-event-property event 'DESCRIPTION) "")
+          (or (icalendar--get-event-property event 'LOCATION) "")
+          (or (icalendar--get-event-property event 'ORGANIZER) "")
+          (or (icalendar--get-event-property event 'STATUS) "")
+          (or (icalendar--get-event-property event 'URL) "")
+          (or (icalendar--get-event-property event 'CLASS) "")))
 
 (provide 'icalendar)