1 ;;; icalendar.el --- iCalendar implementation -*-coding: utf-8 -*-
3 ;; Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
5 ;; Author: Ulf Jasper <ulf.jasper@web.de>
6 ;; Created: August 2002
8 ;; Human-Keywords: calendar, diary, iCalendar, vCalendar
10 ;; This file is part of GNU Emacs.
12 ;; GNU Emacs is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation; either version 2, or (at your option)
17 ;; GNU Emacs is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;; GNU General Public License for more details.
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs; see the file COPYING. If not, write to the
24 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
25 ;; Boston, MA 02110-1301, USA.
29 ;; This package is documented in the Emacs Manual.
32 ;; - Diary entries which have a start time but no end time are assumed to
33 ;; last for one hour when they are exported.
34 ;; - Weekly diary entries are assumed to occur the first time in the first
35 ;; week of the year 2000 when they are exported.
36 ;; - Yearly diary entries are assumed to occur the first time in the year
37 ;; 1900 when they are exported.
41 ;; 0.07 onwards: see lisp/ChangeLog
43 ;; 0.06: Bugfixes regarding icalendar-import-format-*.
44 ;; Fix in icalendar-convert-diary-to-ical -- thanks to Philipp
47 ;; 0.05: New import format scheme: Replaced icalendar-import-prefix-*,
48 ;; icalendar-import-ignored-properties, and
49 ;; icalendar-import-separator with icalendar-import-format(-*).
50 ;; icalendar-import-file and icalendar-convert-diary-to-ical
51 ;; have an extra parameter which should prevent them from
52 ;; erasing their target files (untested!).
53 ;; Tested with Emacs 21.3.2
55 ;; 0.04: Bugfix: import: double quoted param values did not work
56 ;; Read DURATION property when importing.
57 ;; Added parameter icalendar-duration-correction.
59 ;; 0.03: Export takes care of european-calendar-style.
60 ;; Tested with Emacs 21.3.2 and XEmacs 21.4.12
62 ;; 0.02: Should work in XEmacs now. Thanks to Len Trigg for the
64 ;; Added exporting from Emacs diary to ical.
65 ;; Some bugfixes, after testing with calendars from
66 ;; http://icalshare.com.
67 ;; Tested with Emacs 21.3.2 and XEmacs 21.4.12
69 ;; 0.01: First published version. Trial version. Alpha version.
71 ;; ======================================================================
74 ;; * Import from ical to diary:
75 ;; + Need more properties for icalendar-import-format
76 ;; (added all that Mozilla Calendar uses)
77 ;; From iCal specifications (RFC2445: 4.8.1), icalendar.el lacks
78 ;; ATTACH, CATEGORIES, COMMENT, GEO, PERCENT-COMPLETE (VTODO),
79 ;; PRIORITY, RESOURCES) not considering date/time and time-zone
80 ;; + check vcalendar version
81 ;; + check (unknown) elements
82 ;; + recurring events!
83 ;; + works for european style calendars only! Does it?
85 ;; + exceptions in recurring events
86 ;; + the parser is too soft
87 ;; + error log is incomplete
88 ;; + nice to have: #include "webcal://foo.com/some-calendar.ics"
89 ;; + timezones, currently all times are local!
91 ;; * Export from diary to ical
92 ;; + diary-date, diary-float, and self-made sexp entries are not
96 ;; + clean up all those date/time parsing functions
97 ;; + Handle todo items?
98 ;; + Check iso 8601 for datetime and period
99 ;; + Which chars to (un)escape?
104 (defconst icalendar-version
"0.14"
105 "Version number of icalendar.el.")
107 ;; ======================================================================
109 ;; ======================================================================
110 (defgroup icalendar nil
115 (defcustom icalendar-import-format
117 "Format string for importing events from iCalendar into Emacs diary.
118 This string defines how iCalendar events are inserted into diary
119 file. Meaning of the specifiers:
120 %c Class, see `icalendar-import-format-class'
121 %d Description, see `icalendar-import-format-description'
122 %l Location, see `icalendar-import-format-location'
123 %o Organizer, see `icalendar-import-format-organizer'
124 %s Summary, see `icalendar-import-format-summary'
125 %t Status, see `icalendar-import-format-status'
126 %u URL, see `icalendar-import-format-url'"
130 (defcustom icalendar-import-format-summary
132 "Format string defining how the summary element is formatted.
133 This applies only if the summary is not empty! `%s' is replaced
138 (defcustom icalendar-import-format-description
140 "Format string defining how the description element is formatted.
141 This applies only if the description is not empty! `%s' is
142 replaced by the description."
146 (defcustom icalendar-import-format-location
148 "Format string defining how the location element is formatted.
149 This applies only if the location is not empty! `%s' is replaced
154 (defcustom icalendar-import-format-organizer
156 "Format string defining how the organizer element is formatted.
157 This applies only if the organizer is not empty! `%s' is
158 replaced by the organizer."
162 (defcustom icalendar-import-format-url
164 "Format string defining how the URL element is formatted.
165 This applies only if the URL is not empty! `%s' is replaced by
170 (defcustom icalendar-import-format-status
172 "Format string defining how the status element is formatted.
173 This applies only if the status is not empty! `%s' is replaced by
178 (defcustom icalendar-import-format-class
180 "Format string defining how the class element is formatted.
181 This applies only if the class is not empty! `%s' is replaced by
186 (defvar icalendar-debug nil
187 "Enable icalendar debug messages.")
189 ;; ======================================================================
190 ;; NO USER SERVICABLE PARTS BELOW THIS LINE
191 ;; ======================================================================
193 (defconst icalendar--weekday-array
["SU" "MO" "TU" "WE" "TH" "FR" "SA"])
195 ;; ======================================================================
196 ;; all the other libs we need
197 ;; ======================================================================
200 ;; ======================================================================
202 ;; ======================================================================
203 (defun icalendar--dmsg (&rest args
)
204 "Print message ARGS if `icalendar-debug' is non-nil."
206 (apply 'message args
)))
208 ;; ======================================================================
209 ;; Core functionality
210 ;; Functions for parsing icalendars, importing and so on
211 ;; ======================================================================
213 (defun icalendar--get-unfolded-buffer (folded-ical-buffer)
214 "Return a new buffer containing the unfolded contents of a buffer.
215 Folding is the iCalendar way of wrapping long lines. In the
216 created buffer all occurrences of CR LF BLANK are replaced by the
217 empty string. Argument FOLDED-ICAL-BUFFER is the unfolded input
219 (let ((unfolded-buffer (get-buffer-create " *icalendar-work*")))
221 (set-buffer unfolded-buffer
)
223 (insert-buffer-substring folded-ical-buffer
)
224 (goto-char (point-min))
225 (while (re-search-forward "\r?\n[ \t]" nil t
)
226 (replace-match "" nil nil
)))
229 (defsubst icalendar--rris
(&rest args
)
230 "Replace regular expression in string.
231 Pass ARGS to `replace-regexp-in-string' (Emacs) or to
232 `replace-in-string' (XEmacs)."
233 (if (fboundp 'replace-regexp-in-string
)
235 (apply 'replace-regexp-in-string args
)
237 (save-match-data ;; apparently XEmacs needs save-match-data
238 (apply 'replace-in-string args
))))
240 (defun icalendar--read-element (invalue inparams
)
241 "Recursively read the next iCalendar element in the current buffer.
242 INVALUE gives the current iCalendar element we are reading.
243 INPARAMS gives the current parameters.....
244 This function calls itself recursively for each nested calendar element
246 (let (element children line name params param param-name param-value
251 (re-search-forward "^\\([A-Za-z0-9-]+\\)[;:]" nil t
))
252 (setq name
(intern (match-string 1)))
256 (while (looking-at ";")
257 (re-search-forward ";\\([A-Za-z0-9-]+\\)=" nil nil
)
258 (setq param-name
(intern (match-string 1)))
259 (re-search-forward "\\(\\([^;,:\"]+\\)\\|\"\\([^\"]+\\)\"\\)[;:]"
262 (setq param-value
(or (match-string 2) (match-string 3)))
263 (setq param
(list param-name param-value
))
264 (while (looking-at ",")
265 (re-search-forward "\\(\\([^;,:]+\\)\\|\"\\([^\"]+\\)\"\\)"
268 (setq param-value
(match-string 2))
269 (setq param-value
(match-string 3)))
270 (setq param
(append param param-value
)))
271 (setq params
(append params param
)))
272 (unless (looking-at ":")
275 (re-search-forward "\\(.*\\)\\(\r?\n[ \t].*\\)*" nil t
)
276 (setq value
(icalendar--rris "\r?\n[ \t]" "" (match-string 0)))
277 (setq line
(list name params value
))
278 (cond ((eq name
'BEGIN
)
281 (list (icalendar--read-element (intern value
)
286 (setq element
(append element
(list line
))))))
288 (list invalue inparams element children
)
291 ;; ======================================================================
292 ;; helper functions for examining events
293 ;; ======================================================================
295 ;;(defsubst icalendar--get-all-event-properties (event)
296 ;; "Return the list of properties in this EVENT."
297 ;; (car (cddr event)))
299 (defun icalendar--get-event-property (event prop
)
300 "For the given EVENT return the value of the first occurrence of PROP."
302 (let ((props (car (cddr event
))) pp
)
304 (setq pp
(car props
))
305 (if (eq (car pp
) prop
)
306 (throw 'found
(car (cddr pp
))))
307 (setq props
(cdr props
))))
310 (defun icalendar--get-event-property-attributes (event prop
)
311 "For the given EVENT return attributes of the first occurrence of PROP."
313 (let ((props (car (cddr event
))) pp
)
315 (setq pp
(car props
))
316 (if (eq (car pp
) prop
)
317 (throw 'found
(cadr pp
)))
318 (setq props
(cdr props
))))
321 (defun icalendar--get-event-properties (event prop
)
322 "For the given EVENT return a list of all values of the property PROP."
323 (let ((props (car (cddr event
))) pp result
)
325 (setq pp
(car props
))
326 (if (eq (car pp
) prop
)
327 (setq result
(append (split-string (car (cddr pp
)) ",") result
)))
328 (setq props
(cdr props
)))
331 ;; (defun icalendar--set-event-property (event prop new-value)
332 ;; "For the given EVENT set the property PROP to the value NEW-VALUE."
334 ;; (let ((props (car (cddr event))) pp)
336 ;; (setq pp (car props))
337 ;; (when (eq (car pp) prop)
338 ;; (setcdr (cdr pp) new-value)
339 ;; (throw 'found (car (cddr pp))))
340 ;; (setq props (cdr props)))
341 ;; (setq props (car (cddr event)))
342 ;; (setcar (cddr event)
343 ;; (append props (list (list prop nil new-value)))))))
345 (defun icalendar--get-children (node name
)
346 "Return all children of the given NODE which have a name NAME.
347 For instance the VCALENDAR node can have VEVENT children as well as VTODO
350 (children (cadr (cddr node
))))
351 (when (eq (car node
) name
)
353 ;;(message "%s" node)
358 (icalendar--get-children n name
))
362 (setq result
(append result subresult
))
363 (setq result subresult
)))))
367 (defun icalendar--all-events (icalendar)
368 "Return the list of all existing events in the given ICALENDAR."
369 (icalendar--get-children (car icalendar
) 'VEVENT
))
371 (defun icalendar--split-value (value-string)
372 "Split VALUE-STRING at ';='."
374 param-name param-value
)
377 (set-buffer (get-buffer-create " *icalendar-work*"))
378 (set-buffer-modified-p nil
)
380 (insert value-string
)
381 (goto-char (point-min))
384 "\\([A-Za-z0-9-]+\\)=\\(\\([^;,:]+\\)\\|\"\\([^\"]+\\)\"\\);?"
386 (setq param-name
(intern (match-string 1)))
387 (setq param-value
(match-string 2))
389 (append result
(list (list param-name param-value
)))))))
392 (defun icalendar--decode-isodatetime (isodatetimestring &optional day-shift
)
393 "Return ISODATETIMESTRING in format like `decode-time'.
394 Converts from ISO-8601 to Emacs representation. If
395 ISODATETIMESTRING specifies UTC time (trailing letter Z) the
396 decoded time is given in the local time zone! If optional
397 parameter DAY-SHIFT is non-nil the result is shifted by DAY-SHIFT
400 FIXME: TZID-attributes are ignored....!
401 FIXME: multiple comma-separated values should be allowed!"
402 (icalendar--dmsg isodatetimestring
)
403 (if isodatetimestring
404 ;; day/month/year must be present
405 (let ((year (read (substring isodatetimestring
0 4)))
406 (month (read (substring isodatetimestring
4 6)))
407 (day (read (substring isodatetimestring
6 8)))
411 (when (> (length isodatetimestring
) 12)
412 ;; hour/minute present
413 (setq hour
(read (substring isodatetimestring
9 11)))
414 (setq minute
(read (substring isodatetimestring
11 13))))
415 (when (> (length isodatetimestring
) 14)
417 (setq second
(read (substring isodatetimestring
13 15))))
418 (when (and (> (length isodatetimestring
) 15)
419 ;; UTC specifier present
420 (char-equal ?Z
(aref isodatetimestring
15)))
421 ;; if not UTC add current-time-zone offset
422 (setq second
(+ (car (current-time-zone)) second
)))
423 ;; shift if necessary
425 (let ((mdy (calendar-gregorian-from-absolute
426 (+ (calendar-absolute-from-gregorian
427 (list month day year
))
429 (setq month
(nth 0 mdy
))
430 (setq day
(nth 1 mdy
))
431 (setq year
(nth 2 mdy
))))
432 ;; create the decoded date-time
435 (decode-time (encode-time second minute hour day month year
))
437 (message "Cannot decode \"%s\"" isodatetimestring
)
438 ;; hope for the best...
439 (list second minute hour day month year
0 nil
0))))
440 ;; isodatetimestring == nil
443 (defun icalendar--decode-isoduration (isodurationstring
444 &optional duration-correction
)
445 "Convert ISODURATIONSTRING into format provided by `decode-time'.
446 Converts from ISO-8601 to Emacs representation. If ISODURATIONSTRING
447 specifies UTC time (trailing letter Z) the decoded time is given in
450 Optional argument DURATION-CORRECTION shortens result by one day.
452 FIXME: TZID-attributes are ignored....!
453 FIXME: multiple comma-separated values should be allowed!"
454 (if isodurationstring
459 "\\(\\([0-9]+\\)D\\)" ; days only
461 "\\(\\(\\([0-9]+\\)D\\)?T\\(\\([0-9]+\\)H\\)?" ; opt days
462 "\\(\\([0-9]+\\)M\\)?\\(\\([0-9]+\\)S\\)?\\)" ; mand. time
464 "\\(\\([0-9]+\\)W\\)" ; weeks only
465 "\\)$") isodurationstring
)
473 ((match-beginning 2) ;days only
474 (setq days
(read (substring isodurationstring
477 (when duration-correction
478 (setq days
(1- days
))))
479 ((match-beginning 4) ;days and time
480 (if (match-beginning 5)
481 (setq days
(* 7 (read (substring isodurationstring
484 (if (match-beginning 7)
485 (setq hours
(read (substring isodurationstring
488 (if (match-beginning 9)
489 (setq minutes
(read (substring isodurationstring
492 (if (match-beginning 11)
493 (setq seconds
(read (substring isodurationstring
496 ((match-beginning 13) ;weeks only
497 (setq days
(* 7 (read (substring isodurationstring
500 (list seconds minutes hours days months years
)))
501 ;; isodatetimestring == nil
504 (defun icalendar--add-decoded-times (time1 time2
)
506 Both times must be given in decoded form. One of these times must be
507 valid (year > 1900 or something)."
508 ;; FIXME: does this function exist already?
509 (decode-time (encode-time
510 (+ (nth 0 time1
) (nth 0 time2
))
511 (+ (nth 1 time1
) (nth 1 time2
))
512 (+ (nth 2 time1
) (nth 2 time2
))
513 (+ (nth 3 time1
) (nth 3 time2
))
514 (+ (nth 4 time1
) (nth 4 time2
))
515 (+ (nth 5 time1
) (nth 5 time2
))
518 ;;(or (nth 6 time1) (nth 6 time2)) ;; FIXME?
521 (defun icalendar--datetime-to-noneuropean-date (datetime &optional separator
)
522 "Convert the decoded DATETIME to non-european-style format.
523 Optional argument SEPARATOR gives the separator between month,
524 day, and year. If nil a blank character is used as separator.
525 Non-European format: \"month day year\"."
527 (format "%d%s%d%s%d" (nth 4 datetime
) ;month
529 (nth 3 datetime
) ;day
531 (nth 5 datetime
)) ;year
535 (defun icalendar--datetime-to-european-date (datetime &optional separator
)
536 "Convert the decoded DATETIME to European format.
537 Optional argument SEPARATOR gives the separator between month,
538 day, and year. If nil a blank character is used as separator.
539 European format: (day month year).
542 (format "%d%s%d%s%d" (nth 3 datetime
) ;day
544 (nth 4 datetime
) ;month
546 (nth 5 datetime
)) ;year
550 (defun icalendar--datetime-to-diary-date (datetime &optional separator
)
551 "Convert the decoded DATETIME to diary format.
552 Optional argument SEPARATOR gives the separator between month,
553 day, and year. If nil a blank character is used as separator.
554 Call icalendar--datetime-to-(non)-european-date according to
555 value of `european-calendar-style'."
556 (if european-calendar-style
557 (icalendar--datetime-to-european-date datetime separator
)
558 (icalendar--datetime-to-noneuropean-date datetime separator
)))
560 (defun icalendar--datetime-to-colontime (datetime)
561 "Extract the time part of a decoded DATETIME into 24-hour format.
562 Note that this silently ignores seconds."
563 (format "%02d:%02d" (nth 2 datetime
) (nth 1 datetime
)))
565 (defun icalendar--get-month-number (monthname)
566 "Return the month number for the given MONTHNAME."
569 (m (downcase monthname
)))
570 (mapc (lambda (month)
571 (let ((mm (downcase month
)))
572 (if (or (string-equal mm m
)
573 (string-equal (substring mm
0 3) m
))
575 (setq num
(1+ num
))))
576 calendar-month-name-array
))
580 (defun icalendar--get-weekday-number (abbrevweekday)
581 "Return the number for the ABBREVWEEKDAY."
585 (aw (downcase abbrevweekday
)))
587 (let ((d (downcase day
)))
588 (if (string-equal d aw
)
590 (setq num
(1+ num
))))
591 icalendar--weekday-array
)))
595 (defun icalendar--get-weekday-abbrev (weekday)
596 "Return the abbreviated WEEKDAY."
599 (w (downcase weekday
)))
601 (let ((d (downcase day
)))
602 (if (or (string-equal d w
)
603 (string-equal (substring d
0 3) w
))
604 (throw 'found
(aref icalendar--weekday-array num
)))
605 (setq num
(1+ num
))))
606 calendar-day-name-array
))
610 (defun icalendar--date-to-isodate (date &optional day-shift
)
611 "Convert DATE to iso-style date.
612 DATE must be a list of the form (month day year).
613 If DAY-SHIFT is non-nil, the result is shifted by DAY-SHIFT days."
614 (let ((mdy (calendar-gregorian-from-absolute
615 (+ (calendar-absolute-from-gregorian date
)
617 (format "%04d%02d%02d" (nth 2 mdy
) (nth 0 mdy
) (nth 1 mdy
))))
620 (defun icalendar--datestring-to-isodate (datestring &optional day-shift
)
621 "Convert diary-style DATESTRING to iso-style date.
622 If DAY-SHIFT is non-nil, the result is shifted by DAY-SHIFT days
623 -- DAY-SHIFT must be either nil or an integer. This function
624 takes care of european-style."
625 (let ((day -
1) month year
)
627 (cond ( ;; numeric date
628 (string-match (concat "\\s-*"
629 "0?\\([1-9][0-9]?\\)[ \t/]\\s-*"
630 "0?\\([1-9][0-9]?\\),?[ \t/]\\s-*"
631 "\\([0-9]\\{4\\}\\)")
633 (setq day
(read (substring datestring
(match-beginning 1)
635 (setq month
(read (substring datestring
(match-beginning 2)
637 (setq year
(read (substring datestring
(match-beginning 3)
639 (unless european-calendar-style
643 ( ;; date contains month names -- european-style
644 (string-match (concat "\\s-*"
645 "0?\\([123]?[0-9]\\)[ \t/]\\s-*"
646 "\\([A-Za-z][^ ]+\\)[ \t/]\\s-*"
647 "\\([0-9]\\{4\\}\\)")
649 (setq day
(read (substring datestring
(match-beginning 1)
651 (setq month
(icalendar--get-month-number
652 (substring datestring
(match-beginning 2)
654 (setq year
(read (substring datestring
(match-beginning 3)
656 ( ;; date contains month names -- non-european-style
657 (string-match (concat "\\s-*"
658 "\\([A-Za-z][^ ]+\\)[ \t/]\\s-*"
659 "0?\\([123]?[0-9]\\),?[ \t/]\\s-*"
660 "\\([0-9]\\{4\\}\\)")
662 (setq day
(read (substring datestring
(match-beginning 2)
664 (setq month
(icalendar--get-month-number
665 (substring datestring
(match-beginning 1)
667 (setq year
(read (substring datestring
(match-beginning 3)
672 (let ((mdy (calendar-gregorian-from-absolute
673 (+ (calendar-absolute-from-gregorian (list month day
676 (format "%04d%02d%02d" (nth 2 mdy
) (nth 0 mdy
) (nth 1 mdy
)))
679 (defun icalendar--diarytime-to-isotime (timestring ampmstring
)
680 "Convert a a time like 9:30pm to an iso-conform string like T213000.
681 In this example the TIMESTRING would be \"9:30\" and the AMPMSTRING
684 (let ((starttimenum (read (icalendar--rris ":" "" timestring
))))
685 ;; take care of am/pm style
686 (if (and ampmstring
(string= "pm" ampmstring
))
687 (setq starttimenum
(+ starttimenum
1200)))
688 (format "T%04d00" starttimenum
))
691 (defun icalendar--convert-string-for-export (string)
692 "Escape comma and other critical characters in STRING."
693 (icalendar--rris "," "\\\\," string
))
695 (defun icalendar--convert-string-for-import (string)
696 "Remove escape chars for comma, semicolon etc. from STRING."
698 "\\\\n" "\n " (icalendar--rris
699 "\\\\\"" "\"" (icalendar--rris
700 "\\\\;" ";" (icalendar--rris
701 "\\\\," "," string
)))))
703 ;; ======================================================================
704 ;; Export -- convert emacs-diary to icalendar
705 ;; ======================================================================
708 (defun icalendar-export-file (diary-filename ical-filename
)
709 "Export diary file to iCalendar format.
710 All diary entries in the file DIARY-FILENAME are converted to iCalendar
711 format. The result is appended to the file ICAL-FILENAME."
712 (interactive "FExport diary data from file:
713 Finto iCalendar file: ")
715 (set-buffer (find-file diary-filename
))
716 (icalendar-export-region (point-min) (point-max) ical-filename
)))
718 (defalias 'icalendar-convert-diary-to-ical
'icalendar-export-file
)
719 (make-obsolete 'icalendar-convert-diary-to-ical
'icalendar-export-file
)
722 (defun icalendar-export-region (min max ical-filename
)
723 "Export region in diary file to iCalendar format.
724 All diary entries in the region from MIN to MAX in the current buffer are
725 converted to iCalendar format. The result is appended to the file
727 This function attempts to return t if something goes wrong. In this
728 case an error string which describes all the errors and problems is
729 written into the buffer `*icalendar-errors*'."
731 FExport diary data into iCalendar file: ")
740 (nonmarker (concat "^" (regexp-quote diary-nonmarking-symbol
)
742 (other-elements nil
))
743 ;; prepare buffer with error messages
745 (set-buffer (get-buffer-create "*icalendar-errors*"))
751 (while (re-search-forward
752 "^\\([^ \t\n].+\\)\\(\\(\n[ \t].*\\)*\\)" max t
)
753 (setq entry-main
(match-string 1))
754 (if (match-beginning 2)
755 (setq entry-rest
(match-string 2))
756 (setq entry-rest
""))
757 (setq header
(format "\nBEGIN:VEVENT\nUID:emacs%d%d%d"
759 (cadr (current-time))
760 (car (cddr (current-time)))))
761 (condition-case error-val
763 (setq contents-n-summary
764 (icalendar--convert-to-ical nonmarker entry-main
))
765 (setq other-elements
(icalendar--parse-summary-and-rest
766 (concat entry-main entry-rest
)))
767 (setq contents
(concat (car contents-n-summary
)
768 "\nSUMMARY:" (cadr contents-n-summary
)))
769 (let ((cla (cdr (assoc 'cla other-elements
)))
770 (des (cdr (assoc 'des other-elements
)))
771 (loc (cdr (assoc 'loc other-elements
)))
772 (org (cdr (assoc 'org other-elements
)))
773 (sta (cdr (assoc 'sta other-elements
)))
774 (sum (cdr (assoc 'sum other-elements
)))
775 (url (cdr (assoc 'url other-elements
))))
777 (setq contents
(concat contents
"\nCLASS:" cla
)))
779 (setq contents
(concat contents
"\nDESCRIPTION:" des
)))
781 (setq contents
(concat contents
"\nLOCATION:" loc
)))
783 (setq contents
(concat contents
"\nORGANIZER:" org
)))
785 (setq contents
(concat contents
"\nSTATUS:" sta
)))
787 ;; (setq contents (concat contents "\nSUMMARY:" sum)))
789 (setq contents
(concat contents
"\nURL:" url
))))
790 (setq result
(concat result header contents
"\nEND:VEVENT")))
795 (set-buffer (get-buffer-create "*icalendar-errors*"))
796 (insert (format "Error in line %d -- %s: `%s'\n"
797 (count-lines (point-min) (point))
801 ;; we're done, insert everything into the file
803 (let ((coding-system-for-write 'utf-8
))
804 (set-buffer (find-file ical-filename
))
805 (goto-char (point-max))
806 (insert "BEGIN:VCALENDAR")
807 (insert "\nPRODID:-//Emacs//NONSGML icalendar.el//EN")
808 (insert "\nVERSION:2.0")
810 (insert "\nEND:VCALENDAR\n")
811 ;; save the diary file
817 (defun icalendar--convert-to-ical (nonmarker entry-main
)
818 "Convert a diary entry to icalendar format.
819 NONMARKER is a regular expression matching the start of non-marking
820 entries. ENTRY-MAIN is the first line of the diary entry."
822 ;; anniversaries -- %%(diary-anniversary ...)
823 (icalendar--convert-anniversary-to-ical nonmarker entry-main
)
824 ;; cyclic events -- %%(diary-cyclic ...)
825 (icalendar--convert-cyclic-to-ical nonmarker entry-main
)
826 ;; diary-date -- %%(diary-date ...)
827 (icalendar--convert-date-to-ical nonmarker entry-main
)
828 ;; float events -- %%(diary-float ...)
829 (icalendar--convert-float-to-ical nonmarker entry-main
)
830 ;; block events -- %%(diary-block ...)
831 (icalendar--convert-block-to-ical nonmarker entry-main
)
832 ;; other sexp diary entries
833 (icalendar--convert-sexp-to-ical nonmarker entry-main
)
834 ;; weekly by day -- Monday 8:30 Team meeting
835 (icalendar--convert-weekly-to-ical nonmarker entry-main
)
836 ;; yearly by day -- 1 May Tag der Arbeit
837 (icalendar--convert-yearly-to-ical nonmarker entry-main
)
838 ;; "ordinary" events, start and end time given
840 (icalendar--convert-ordinary-to-ical nonmarker entry-main
)
842 ;; Oops! what's that?
843 (error "Could not parse entry")))
845 (defun icalendar--parse-summary-and-rest (summary-and-rest)
846 "Parse SUMMARY-AND-REST from a diary to fill iCalendar properties."
848 (let* ((s icalendar-import-format
)
849 (p-cla (or (string-match "%c" icalendar-import-format
) -
1))
850 (p-des (or (string-match "%d" icalendar-import-format
) -
1))
851 (p-loc (or (string-match "%l" icalendar-import-format
) -
1))
852 (p-org (or (string-match "%o" icalendar-import-format
) -
1))
853 (p-sum (or (string-match "%s" icalendar-import-format
) -
1))
854 (p-sta (or (string-match "%t" icalendar-import-format
) -
1))
855 (p-url (or (string-match "%u" icalendar-import-format
) -
1))
856 (p-list (sort (list p-cla p-des p-loc p-org p-sta p-sum p-url
) '<))
857 pos-cla pos-des pos-loc pos-org pos-sta pos-sum pos-url
)
858 (dotimes (i (length p-list
))
859 (cond ((and (>= p-cla
0) (= (nth i p-list
) p-cla
))
860 (setq pos-cla
(+ 2 (* 2 i
))))
861 ((and (>= p-des
0) (= (nth i p-list
) p-des
))
862 (setq pos-des
(+ 2 (* 2 i
))))
863 ((and (>= p-loc
0) (= (nth i p-list
) p-loc
))
864 (setq pos-loc
(+ 2 (* 2 i
))))
865 ((and (>= p-org
0) (= (nth i p-list
) p-org
))
866 (setq pos-org
(+ 2 (* 2 i
))))
867 ((and (>= p-sta
0) (= (nth i p-list
) p-sta
))
868 (setq pos-sta
(+ 2 (* 2 i
))))
869 ((and (>= p-sum
0) (= (nth i p-list
) p-sum
))
870 (setq pos-sum
(+ 2 (* 2 i
))))
871 ((and (>= p-url
0) (= (nth i p-list
) p-url
))
872 (setq pos-url
(+ 2 (* 2 i
))))))
874 (setq s
(icalendar--rris (car ij
) (cadr ij
) s t t
)))
876 ;; summary must be first! because of %s
878 (concat "\\(" icalendar-import-format-summary
"\\)?"))
880 (concat "\\(" icalendar-import-format-class
"\\)?"))
882 (concat "\\(" icalendar-import-format-description
"\\)?"))
884 (concat "\\(" icalendar-import-format-location
"\\)?"))
886 (concat "\\(" icalendar-import-format-organizer
"\\)?"))
888 (concat "\\(" icalendar-import-format-status
"\\)?"))
890 (concat "\\(" icalendar-import-format-url
"\\)?"))))
891 (setq s
(concat (icalendar--rris "%s" "\\(.*\\)" s nil t
) " "))
892 (if (string-match s summary-and-rest
)
893 (let (cla des loc org sta sum url
)
894 (if (and pos-sum
(match-beginning pos-sum
))
895 (setq sum
(substring summary-and-rest
896 (match-beginning pos-sum
)
897 (match-end pos-sum
))))
898 (if (and pos-cla
(match-beginning pos-cla
))
899 (setq cla
(substring summary-and-rest
900 (match-beginning pos-cla
)
901 (match-end pos-cla
))))
902 (if (and pos-des
(match-beginning pos-des
))
903 (setq des
(substring summary-and-rest
904 (match-beginning pos-des
)
905 (match-end pos-des
))))
906 (if (and pos-loc
(match-beginning pos-loc
))
907 (setq loc
(substring summary-and-rest
908 (match-beginning pos-loc
)
909 (match-end pos-loc
))))
910 (if (and pos-org
(match-beginning pos-org
))
911 (setq org
(substring summary-and-rest
912 (match-beginning pos-org
)
913 (match-end pos-org
))))
914 (if (and pos-sta
(match-beginning pos-sta
))
915 (setq sta
(substring summary-and-rest
916 (match-beginning pos-sta
)
917 (match-end pos-sta
))))
918 (if (and pos-url
(match-beginning pos-url
))
919 (setq url
(substring summary-and-rest
920 (match-beginning pos-url
)
921 (match-end pos-url
))))
922 (list (if cla
(cons 'cla cla
) nil
)
923 (if des
(cons 'des des
) nil
)
924 (if loc
(cons 'loc loc
) nil
)
925 (if org
(cons 'org org
) nil
)
926 (if sta
(cons 'sta sta
) nil
)
927 ;;(if sum (cons 'sum sum) nil)
928 (if url
(cons 'url url
) nil
)))))))
930 ;; subroutines for icalendar-export-region
931 (defun icalendar--convert-ordinary-to-ical (nonmarker entry-main
)
932 "Convert \"ordinary\" diary entry to icalendar format.
933 NONMARKER is a regular expression matching the start of non-marking
934 entries. ENTRY-MAIN is the first line of the diary entry."
935 (if (string-match (concat nonmarker
936 "\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\)\\s-*"
937 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
939 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
943 (let* ((datetime (substring entry-main
(match-beginning 1)
945 (startisostring (icalendar--datestring-to-isodate
947 (endisostring (icalendar--datestring-to-isodate
949 (starttimestring (icalendar--diarytime-to-isotime
950 (if (match-beginning 3)
951 (substring entry-main
955 (if (match-beginning 4)
956 (substring entry-main
960 (endtimestring (icalendar--diarytime-to-isotime
961 (if (match-beginning 6)
962 (substring entry-main
966 (if (match-beginning 7)
967 (substring entry-main
971 (summary (icalendar--convert-string-for-export
972 (substring entry-main
(match-beginning 8)
974 (icalendar--dmsg "ordinary %s" entry-main
)
976 (unless startisostring
977 (error "Could not parse date"))
978 (when starttimestring
979 (unless endtimestring
981 (read (icalendar--rris "^T0?" ""
983 (setq endtimestring
(format "T%06d"
985 (list (concat "\nDTSTART;"
986 (if starttimestring
"VALUE=DATE-TIME:"
989 (or starttimestring
"")
991 (if endtimestring
"VALUE=DATE-TIME:"
996 (or endtimestring
""))
1001 (defun icalendar--convert-weekly-to-ical (nonmarker entry-main
)
1002 "Convert weekly diary entry to icalendar format.
1003 NONMARKER is a regular expression matching the start of non-marking
1004 entries. ENTRY-MAIN is the first line of the diary entry."
1005 (if (and (string-match (concat nonmarker
1007 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)"
1010 "\\([1-9][0-9]?:[0-9][0-9]\\)"
1013 "\\s-*\\(.*?\\) ?$")
1015 (icalendar--get-weekday-abbrev
1016 (substring entry-main
(match-beginning 1)
1018 (let* ((day (icalendar--get-weekday-abbrev
1019 (substring entry-main
(match-beginning 1)
1021 (starttimestring (icalendar--diarytime-to-isotime
1022 (if (match-beginning 3)
1023 (substring entry-main
1027 (if (match-beginning 4)
1028 (substring entry-main
1032 (endtimestring (icalendar--diarytime-to-isotime
1033 (if (match-beginning 6)
1034 (substring entry-main
1038 (if (match-beginning 7)
1039 (substring entry-main
1043 (summary (icalendar--convert-string-for-export
1044 (substring entry-main
(match-beginning 8)
1046 (icalendar--dmsg "weekly %s" entry-main
)
1048 (when starttimestring
1049 (unless endtimestring
1051 (icalendar--rris "^T0?" ""
1053 (setq endtimestring
(format "T%06d"
1055 (list (concat "\nDTSTART;"
1059 ;; find the correct week day,
1060 ;; 1st january 2000 was a saturday
1063 (+ (icalendar--get-weekday-number day
) 2))
1064 (or starttimestring
"")
1071 ;; end is non-inclusive!
1072 (+ (icalendar--get-weekday-number day
)
1073 (if endtimestring
2 3)))
1074 (or endtimestring
"")
1075 "\nRRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY="
1081 (defun icalendar--convert-yearly-to-ical (nonmarker entry-main
)
1082 "Convert yearly diary entry to icalendar format.
1083 NONMARKER is a regular expression matching the start of non-marking
1084 entries. ENTRY-MAIN is the first line of the diary entry."
1085 (if (string-match (concat nonmarker
1086 (if european-calendar-style
1087 "0?\\([1-9]+[0-9]?\\)\\s-+\\([a-z]+\\)\\s-+"
1088 "\\([a-z]+\\)\\s-+0?\\([1-9]+[0-9]?\\)\\s-+")
1090 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
1092 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1094 "\\s-*\\([^0-9]+.*?\\) ?$" ; must not match years
1097 (let* ((daypos (if european-calendar-style
1 2))
1098 (monpos (if european-calendar-style
2 1))
1099 (day (read (substring entry-main
1100 (match-beginning daypos
)
1101 (match-end daypos
))))
1102 (month (icalendar--get-month-number
1103 (substring entry-main
1104 (match-beginning monpos
)
1105 (match-end monpos
))))
1106 (starttimestring (icalendar--diarytime-to-isotime
1107 (if (match-beginning 4)
1108 (substring entry-main
1112 (if (match-beginning 5)
1113 (substring entry-main
1117 (endtimestring (icalendar--diarytime-to-isotime
1118 (if (match-beginning 7)
1119 (substring entry-main
1123 (if (match-beginning 8)
1124 (substring entry-main
1128 (summary (icalendar--convert-string-for-export
1129 (substring entry-main
(match-beginning 9)
1131 (icalendar--dmsg "yearly %s" entry-main
)
1133 (when starttimestring
1134 (unless endtimestring
1136 (icalendar--rris "^T0?" ""
1138 (setq endtimestring
(format "T%06d"
1140 (list (concat "\nDTSTART;"
1141 (if starttimestring
"VALUE=DATE-TIME:"
1143 (format "1900%02d%02d" month day
)
1144 (or starttimestring
"")
1146 (if endtimestring
"VALUE=DATE-TIME:"
1148 ;; end is not included! shift by one day
1149 (icalendar--date-to-isodate
1150 (list month day
1900)
1151 (if endtimestring
0 1))
1152 (or endtimestring
"")
1153 "\nRRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH="
1154 (format "%2d" month
)
1161 (defun icalendar--convert-sexp-to-ical (nonmarker entry-main
)
1162 "Convert complex sexp diary entry to icalendar format -- unsupported!
1166 NONMARKER is a regular expression matching the start of non-marking
1167 entries. ENTRY-MAIN is the first line of the diary entry."
1168 (cond ((string-match (concat nonmarker
1169 "%%(and \\(([^)]+)\\))\\(\\s-*.*?\\) ?$")
1171 ;; simple sexp entry as generated by icalendar.el: strip off the
1172 ;; unnecessary (and)
1173 (icalendar--dmsg "diary-sexp from icalendar.el %s" entry-main
)
1174 (icalendar--convert-to-ical
1177 (substring entry-main
(match-beginning 1) (match-end 1))
1178 (substring entry-main
(match-beginning 2) (match-end 2)))))
1179 ((string-match (concat nonmarker
1182 (icalendar--dmsg "diary-sexp %s" entry-main
)
1183 (error "Sexp-entries are not supported yet"))
1188 (defun icalendar--convert-block-to-ical (nonmarker entry-main
)
1189 "Convert block diary entry to icalendar format.
1190 NONMARKER is a regular expression matching the start of non-marking
1191 entries. ENTRY-MAIN is the first line of the diary entry."
1192 (if (string-match (concat nonmarker
1193 "%%(diary-block \\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\)"
1194 " +\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\))\\s-*"
1195 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
1197 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1199 "\\s-*\\(.*?\\) ?$")
1201 (let* ((startstring (substring entry-main
1204 (endstring (substring entry-main
1207 (startisostring (icalendar--datestring-to-isodate
1209 (endisostring (icalendar--datestring-to-isodate
1211 (endisostring+1 (icalendar--datestring-to-isodate
1213 (starttimestring (icalendar--diarytime-to-isotime
1214 (if (match-beginning 4)
1215 (substring entry-main
1219 (if (match-beginning 5)
1220 (substring entry-main
1224 (endtimestring (icalendar--diarytime-to-isotime
1225 (if (match-beginning 7)
1226 (substring entry-main
1230 (if (match-beginning 8)
1231 (substring entry-main
1235 (summary (icalendar--convert-string-for-export
1236 (substring entry-main
(match-beginning 9)
1238 (icalendar--dmsg "diary-block %s" entry-main
)
1239 (when starttimestring
1240 (unless endtimestring
1242 (read (icalendar--rris "^T0?" ""
1244 (setq endtimestring
(format "T%06d"
1247 ;; with time -> write rrule
1248 (list (concat "\nDTSTART;VALUE=DATE-TIME:"
1251 "\nDTEND;VALUE=DATE-TIME:"
1254 "\nRRULE:FREQ=DAILY;INTERVAL=1;UNTIL="
1257 ;; no time -> write long event
1258 (list (concat "\nDTSTART;VALUE=DATE:" startisostring
1259 "\nDTEND;VALUE=DATE:" endisostring
+1)
1264 (defun icalendar--convert-float-to-ical (nonmarker entry-main
)
1265 "Convert float diary entry to icalendar format -- unsupported!
1269 NONMARKER is a regular expression matching the start of non-marking
1270 entries. ENTRY-MAIN is the first line of the diary entry."
1271 (if (string-match (concat nonmarker
1272 "%%(diary-float \\([^)]+\\))\\s-*\\(.*?\\) ?$")
1275 (icalendar--dmsg "diary-float %s" entry-main
)
1276 (error "`diary-float' is not supported yet"))
1280 (defun icalendar--convert-date-to-ical (nonmarker entry-main
)
1281 "Convert `diary-date' diary entry to icalendar format -- unsupported!
1285 NONMARKER is a regular expression matching the start of non-marking
1286 entries. ENTRY-MAIN is the first line of the diary entry."
1287 (if (string-match (concat nonmarker
1288 "%%(diary-date \\([^)]+\\))\\s-*\\(.*?\\) ?$")
1291 (icalendar--dmsg "diary-date %s" entry-main
)
1292 (error "`diary-date' is not supported yet"))
1296 (defun icalendar--convert-cyclic-to-ical (nonmarker entry-main
)
1297 "Convert `diary-cyclic' diary entry to icalendar format.
1298 NONMARKER is a regular expression matching the start of non-marking
1299 entries. ENTRY-MAIN is the first line of the diary entry."
1300 (if (string-match (concat nonmarker
1301 "%%(diary-cyclic \\([^ ]+\\) +"
1302 "\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\))\\s-*"
1303 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
1305 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1307 "\\s-*\\(.*?\\) ?$")
1309 (let* ((frequency (substring entry-main
(match-beginning 1)
1311 (datetime (substring entry-main
(match-beginning 2)
1313 (startisostring (icalendar--datestring-to-isodate
1315 (endisostring (icalendar--datestring-to-isodate
1317 (endisostring+1 (icalendar--datestring-to-isodate
1319 (starttimestring (icalendar--diarytime-to-isotime
1320 (if (match-beginning 4)
1321 (substring entry-main
1325 (if (match-beginning 5)
1326 (substring entry-main
1330 (endtimestring (icalendar--diarytime-to-isotime
1331 (if (match-beginning 7)
1332 (substring entry-main
1336 (if (match-beginning 8)
1337 (substring entry-main
1341 (summary (icalendar--convert-string-for-export
1342 (substring entry-main
(match-beginning 9)
1344 (icalendar--dmsg "diary-cyclic %s" entry-main
)
1345 (when starttimestring
1346 (unless endtimestring
1348 (read (icalendar--rris "^T0?" ""
1350 (setq endtimestring
(format "T%06d"
1352 (list (concat "\nDTSTART;"
1353 (if starttimestring
"VALUE=DATE-TIME:"
1356 (or starttimestring
"")
1358 (if endtimestring
"VALUE=DATE-TIME:"
1360 (if endtimestring endisostring endisostring
+1)
1361 (or endtimestring
"")
1362 "\nRRULE:FREQ=DAILY;INTERVAL=" frequency
1363 ;; strange: korganizer does not expect
1364 ;; BYSOMETHING here...
1370 (defun icalendar--convert-anniversary-to-ical (nonmarker entry-main
)
1371 "Convert `diary-anniversary' diary entry to icalendar format.
1372 NONMARKER is a regular expression matching the start of non-marking
1373 entries. ENTRY-MAIN is the first line of the diary entry."
1374 (if (string-match (concat nonmarker
1375 "%%(diary-anniversary \\([^)]+\\))\\s-*"
1376 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
1378 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1380 "\\s-*\\(.*?\\) ?$")
1382 (let* ((datetime (substring entry-main
(match-beginning 1)
1384 (startisostring (icalendar--datestring-to-isodate
1386 (endisostring (icalendar--datestring-to-isodate
1388 (starttimestring (icalendar--diarytime-to-isotime
1389 (if (match-beginning 3)
1390 (substring entry-main
1394 (if (match-beginning 4)
1395 (substring entry-main
1399 (endtimestring (icalendar--diarytime-to-isotime
1400 (if (match-beginning 6)
1401 (substring entry-main
1405 (if (match-beginning 7)
1406 (substring entry-main
1410 (summary (icalendar--convert-string-for-export
1411 (substring entry-main
(match-beginning 8)
1413 (icalendar--dmsg "diary-anniversary %s" entry-main
)
1414 (when starttimestring
1415 (unless endtimestring
1417 (read (icalendar--rris "^T0?" ""
1419 (setq endtimestring
(format "T%06d"
1421 (list (concat "\nDTSTART;"
1422 (if starttimestring
"VALUE=DATE-TIME:"
1425 (or starttimestring
"")
1427 (if endtimestring
"VALUE=DATE-TIME:"
1430 (or endtimestring
"")
1431 "\nRRULE:FREQ=YEARLY;INTERVAL=1"
1432 ;; the following is redundant,
1433 ;; but korganizer seems to expect this... ;(
1434 ;; and evolution doesn't understand it... :(
1435 ;; so... who is wrong?!
1437 (substring startisostring
4 6)
1439 (substring startisostring
6 8))
1444 ;; ======================================================================
1445 ;; Import -- convert icalendar to emacs-diary
1446 ;; ======================================================================
1449 (defun icalendar-import-file (ical-filename diary-filename
1450 &optional non-marking
)
1451 "Import an iCalendar file and append to a diary file.
1452 Argument ICAL-FILENAME output iCalendar file.
1453 Argument DIARY-FILENAME input `diary-file'.
1454 Optional argument NON-MARKING determines whether events are created as
1455 non-marking or not."
1456 (interactive "fImport iCalendar data from file:
1459 ;; clean up the diary file
1460 (save-current-buffer
1461 ;; now load and convert from the ical file
1462 (set-buffer (find-file ical-filename
))
1463 (icalendar-import-buffer diary-filename t non-marking
)))
1466 (defun icalendar-import-buffer (&optional diary-file do-not-ask
1468 "Extract iCalendar events from current buffer.
1470 This function searches the current buffer for the first iCalendar
1471 object, reads it and adds all VEVENT elements to the diary
1474 It will ask for each appointment whether to add it to the diary
1475 when DO-NOT-ASK is non-nil. When called interactively,
1476 DO-NOT-ASK is set to t, so that you are asked fore each event.
1478 NON-MARKING determines whether diary events are created as
1481 Return code t means that importing worked well, return code nil
1482 means that an error has occurred. Error messages will be in the
1483 buffer `*icalendar-errors*'."
1485 (save-current-buffer
1487 (message "Preparing icalendar...")
1488 (set-buffer (icalendar--get-unfolded-buffer (current-buffer)))
1489 (goto-char (point-min))
1490 (message "Preparing icalendar...done")
1491 (if (re-search-forward "^BEGIN:VCALENDAR\\s-*$" nil t
)
1492 (let (ical-contents ical-errors
)
1494 (message "Reading icalendar...")
1496 (setq ical-contents
(icalendar--read-element nil nil
))
1497 (message "Reading icalendar...done")
1499 (message "Converting icalendar...")
1500 (setq ical-errors
(icalendar--convert-ical-to-diary
1502 diary-file do-not-ask non-marking
))
1504 ;; save the diary file if it is visited already
1505 (let ((b (find-buffer-visiting diary-file
)))
1507 (save-current-buffer
1510 (message "Converting icalendar...done")
1511 ;; return t if no error occurred
1514 "Current buffer does not contain icalendar contents!")
1515 ;; return nil, i.e. import did not work
1518 (defalias 'icalendar-extract-ical-from-buffer
'icalendar-import-buffer
)
1519 (make-obsolete 'icalendar-extract-ical-from-buffer
'icalendar-import-buffer
)
1521 (defun icalendar--format-ical-event (event)
1522 "Create a string representation of an iCalendar EVENT."
1523 (let ((string icalendar-import-format
)
1525 '(("%c" CLASS icalendar-import-format-class
)
1526 ("%d" DESCRIPTION icalendar-import-format-description
)
1527 ("%l" LOCATION icalendar-import-format-location
)
1528 ("%o" ORGANIZER icalendar-import-format-organizer
)
1529 ("%s" SUMMARY icalendar-import-format-summary
)
1530 ("%t" STATUS icalendar-import-format-status
)
1531 ("%u" URL icalendar-import-format-url
))))
1532 ;; convert the specifiers in the format string
1534 (let* ((spec (car i
))
1536 (format (car (cddr i
)))
1537 (contents (icalendar--get-event-property event prop
))
1538 (formatted-contents ""))
1539 (when (and contents
(> (length contents
) 0))
1540 (setq formatted-contents
1541 (icalendar--rris "%s"
1542 (icalendar--convert-string-for-import
1544 (symbol-value format
)
1546 (setq string
(icalendar--rris spec
1553 (defun icalendar--convert-ical-to-diary (ical-list diary-file
1554 &optional do-not-ask
1556 "Convert iCalendar data to an Emacs diary file.
1557 Import VEVENTS from the iCalendar object ICAL-LIST and saves them to a
1558 DIARY-FILE. If DO-NOT-ASK is nil the user is asked for each event
1559 whether to actually import it. NON-MARKING determines whether diary
1560 events are created as non-marking.
1561 This function attempts to return t if something goes wrong. In this
1562 case an error string which describes all the errors and problems is
1563 written into the buffer `*icalendar-errors*'."
1564 (let* ((ev (icalendar--all-events ical-list
))
1569 ;; step through all events/appointments
1574 (condition-case error-val
1575 (let* ((dtstart (icalendar--get-event-property e
'DTSTART
))
1576 (dtstart-dec (icalendar--decode-isodatetime dtstart
))
1577 (start-d (icalendar--datetime-to-diary-date
1579 (start-t (icalendar--datetime-to-colontime dtstart-dec
))
1580 (dtend (icalendar--get-event-property e
'DTEND
))
1581 (dtend-dec (icalendar--decode-isodatetime dtend
))
1582 (dtend-1-dec (icalendar--decode-isodatetime dtend -
1))
1586 (summary (icalendar--convert-string-for-import
1587 (or (icalendar--get-event-property e
'SUMMARY
)
1589 (rrule (icalendar--get-event-property e
'RRULE
))
1590 (rdate (icalendar--get-event-property e
'RDATE
))
1591 (duration (icalendar--get-event-property e
'DURATION
)))
1592 (icalendar--dmsg "%s: `%s'" start-d summary
)
1593 ;; check whether start-time is missing
1596 (cadr (icalendar--get-event-property-attributes
1601 (let ((dtend-dec-d (icalendar--add-decoded-times
1603 (icalendar--decode-isoduration duration
)))
1604 (dtend-1-dec-d (icalendar--add-decoded-times
1606 (icalendar--decode-isoduration duration
1608 (if (and dtend-dec
(not (eq dtend-dec dtend-dec-d
)))
1609 (message "Inconsistent endtime and duration for %s"
1611 (setq dtend-dec dtend-dec-d
)
1612 (setq dtend-1-dec dtend-1-dec-d
)))
1613 (setq end-d
(if dtend-dec
1614 (icalendar--datetime-to-diary-date dtend-dec
)
1616 (setq end-1-d
(if dtend-1-dec
1617 (icalendar--datetime-to-diary-date dtend-1-dec
)
1619 (setq end-t
(if (and
1623 (icalendar--get-event-property-attributes
1626 (icalendar--datetime-to-colontime dtend-dec
)
1628 (icalendar--dmsg "start-d: %s, end-d: %s" start-d end-d
)
1633 (icalendar--convert-recurring-to-diary e dtstart-dec start-t
1637 (icalendar--dmsg "rdate event")
1638 (setq diary-string
"")
1639 (mapcar (lambda (datestring)
1641 (concat diary-string
1642 (format "......"))))
1643 (icalendar--split-value rdate
)))
1644 ;; non-recurring event
1646 ((not (string= start-d end-d
))
1648 (icalendar--convert-non-recurring-all-day-to-diary
1652 ((and start-t
(or (not end-t
)
1653 (not (string= start-t end-t
))))
1655 (icalendar--convert-non-recurring-not-all-day-to-diary
1656 e dtstart-dec dtend-dec start-t end-t
))
1660 (icalendar--dmsg "all day event")
1661 (setq diary-string
(icalendar--datetime-to-diary-date
1664 ;; add all other elements unless the user doesn't want to have
1669 (concat diary-string
" "
1670 (icalendar--format-ical-event e
)))
1671 (if do-not-ask
(setq summary nil
))
1672 (icalendar--add-diary-entry diary-string diary-file
1673 non-marking summary
))
1675 (setq found-error t
)
1677 (format "%s\nCannot handle this event:%s"
1679 ;; FIXME: inform user about ignored event properties
1682 (message "Ignoring event \"%s\"" e
)
1683 (setq found-error t
)
1684 (setq error-string
(format "%s\n%s\nCannot handle this event: %s"
1685 error-val error-string e
))
1686 (message "%s" error-string
))))
1687 ;; insert final newline
1688 (let ((b (find-buffer-visiting diary-file
)))
1690 (save-current-buffer
1692 (goto-char (point-max))
1695 (save-current-buffer
1696 (set-buffer (get-buffer-create "*icalendar-errors*"))
1698 (insert error-string
)))
1699 (message "Converting icalendar...done")
1702 ;; subroutines for importing
1703 (defun icalendar--convert-recurring-to-diary (e dtstart-dec start-t end-t
)
1704 "Convert recurring icalendar event E to diary format.
1706 DTSTART-DEC is the DTSTART property of E.
1707 START-T is the event's start time in diary format.
1708 END-T is the event's end time in diary format."
1709 (icalendar--dmsg "recurring event")
1710 (let* ((rrule (icalendar--get-event-property e
'RRULE
))
1711 (rrule-props (icalendar--split-value rrule
))
1712 (frequency (cadr (assoc 'FREQ rrule-props
)))
1713 (until (cadr (assoc 'UNTIL rrule-props
)))
1714 (count (cadr (assoc 'COUNT rrule-props
)))
1715 (interval (read (or (cadr (assoc 'INTERVAL rrule-props
)) "1")))
1716 (dtstart-conv (icalendar--datetime-to-diary-date dtstart-dec
))
1717 (until-conv (icalendar--datetime-to-diary-date
1718 (icalendar--decode-isodatetime until
)))
1719 (until-1-conv (icalendar--datetime-to-diary-date
1720 (icalendar--decode-isodatetime until -
1)))
1723 ;; FIXME FIXME interval!!!!!!!!!!!!!
1727 (message "Must not have UNTIL and COUNT -- ignoring COUNT element!")
1729 (cond ((string-equal frequency
"DAILY")
1730 (setq until
(icalendar--add-decoded-times
1732 (list 0 0 0 (* (read count
) interval
) 0 0)))
1733 (setq until-1
(icalendar--add-decoded-times
1735 (list 0 0 0 (* (- (read count
) 1) interval
)
1738 ((string-equal frequency
"WEEKLY")
1739 (setq until
(icalendar--add-decoded-times
1741 (list 0 0 0 (* (read count
) 7 interval
) 0 0)))
1742 (setq until-1
(icalendar--add-decoded-times
1744 (list 0 0 0 (* (- (read count
) 1) 7
1747 ((string-equal frequency
"MONTHLY")
1748 (setq until
(icalendar--add-decoded-times
1749 dtstart-dec
(list 0 0 0 0 (* (- (read count
) 1)
1751 (setq until-1
(icalendar--add-decoded-times
1752 dtstart-dec
(list 0 0 0 0 (* (- (read count
) 1)
1755 ((string-equal frequency
"YEARLY")
1756 (setq until
(icalendar--add-decoded-times
1757 dtstart-dec
(list 0 0 0 0 0 (* (- (read count
) 1)
1759 (setq until-1
(icalendar--add-decoded-times
1761 (list 0 0 0 0 0 (* (- (read count
) 1)
1765 (message "Cannot handle COUNT attribute for `%s' events."
1767 (setq until-conv
(icalendar--datetime-to-diary-date until
))
1768 (setq until-1-conv
(icalendar--datetime-to-diary-date until-1
))
1771 (cond ((string-equal frequency
"WEEKLY")
1774 ;; weekly and all-day
1775 (icalendar--dmsg "weekly all-day")
1780 "(diary-cyclic %d %s) "
1781 "(diary-block %s %s))")
1785 (if count until-1-conv until-conv
)
1788 (format "%%%%(and (diary-cyclic %d %s))"
1791 ;; weekly and not all-day
1792 (let* ((byday (cadr (assoc 'BYDAY rrule-props
)))
1794 (icalendar--get-weekday-number byday
)))
1795 (icalendar--dmsg "weekly not-all-day")
1800 "(diary-cyclic %d %s) "
1801 "(diary-block %s %s)) "
1808 (if end-t
"-" "") (or end-t
"")))
1811 ;; DTSTART;VALUE=DATE-TIME:20030919T090000
1812 ;; DTEND;VALUE=DATE-TIME:20030919T113000
1815 "%%%%(and (diary-cyclic %s %s)) %s%s%s"
1819 (if end-t
"-" "") (or end-t
"")))))))
1821 ((string-equal frequency
"YEARLY")
1822 (icalendar--dmsg "yearly")
1824 (setq result
(format
1825 (concat "%%%%(and (diary-date %s %s t) "
1826 "(diary-block %s %s)) %s%s%s")
1827 (if european-calendar-style
(nth 3 dtstart-dec
)
1828 (nth 4 dtstart-dec
))
1829 (if european-calendar-style
(nth 4 dtstart-dec
)
1830 (nth 3 dtstart-dec
))
1834 (if end-t
"-" "") (or end-t
"")))
1835 (setq result
(format
1836 "%%%%(and (diary-anniversary %s)) %s%s%s"
1839 (if end-t
"-" "") (or end-t
"")))))
1841 ((string-equal frequency
"MONTHLY")
1842 (icalendar--dmsg "monthly")
1845 "%%%%(and (diary-date %s %s %s) (diary-block %s %s)) %s%s%s"
1846 (if european-calendar-style
(nth 3 dtstart-dec
) "t")
1847 (if european-calendar-style
"t" (nth 3 dtstart-dec
))
1852 "1 1 9999") ;; FIXME: should be unlimited
1854 (if end-t
"-" "") (or end-t
""))))
1856 ((and (string-equal frequency
"DAILY"))
1860 (concat "%%%%(and (diary-cyclic %s %s) "
1861 "(diary-block %s %s)) %s%s%s")
1862 interval dtstart-conv dtstart-conv
1863 (if count until-1-conv until-conv
)
1865 (if end-t
"-" "") (or end-t
"")))
1868 "%%%%(and (diary-cyclic %s %s)) %s%s%s"
1872 (if end-t
"-" "") (or end-t
""))))))
1873 ;; Handle exceptions from recurrence rules
1874 (let ((ex-dates (icalendar--get-event-properties e
'EXDATE
)))
1876 (let* ((ex-start (icalendar--decode-isodatetime
1878 (ex-d (icalendar--datetime-to-diary-date
1881 (icalendar--rris "^%%(\\(and \\)?"
1883 "%%%%(and (not (diary-date %s)) "
1886 (setq ex-dates
(cdr ex-dates
))))
1887 ;; FIXME: exception rules are not recognized
1888 (if (icalendar--get-event-property e
'EXRULE
)
1891 "\n Exception rules: "
1892 (icalendar--get-event-properties
1896 (defun icalendar--convert-non-recurring-all-day-to-diary (event start-d end-d
)
1897 "Convert non-recurring icalendar EVENT to diary format.
1899 DTSTART is the decoded DTSTART property of E.
1900 Argument START-D gives the first day.
1901 Argument END-D gives the last day."
1902 (icalendar--dmsg "non-recurring all-day event")
1903 (format "%%%%(and (diary-block %s %s))" start-d end-d
))
1905 (defun icalendar--convert-non-recurring-not-all-day-to-diary (event dtstart-dec
1909 "Convert recurring icalendar EVENT to diary format.
1911 DTSTART-DEC is the decoded DTSTART property of E.
1912 DTEND-DEC is the decoded DTEND property of E.
1913 START-T is the event's start time in diary format.
1914 END-T is the event's end time in diary format."
1915 (icalendar--dmsg "not all day event")
1918 (icalendar--datetime-to-diary-date
1923 (icalendar--datetime-to-diary-date
1927 (defun icalendar--add-diary-entry (string diary-file non-marking
1929 "Add STRING to the diary file DIARY-FILE.
1930 STRING must be a properly formatted valid diary entry. NON-MARKING
1931 determines whether diary events are created as non-marking. If
1932 SUMMARY is not nil it must be a string that gives the summary of the
1933 entry. In this case the user will be asked whether he wants to insert
1935 (when (or (not summary
)
1936 (y-or-n-p (format "Add appointment for `%s' to diary? "
1940 (y-or-n-p (format "Make appointment non-marking? "))))
1941 (save-window-excursion
1944 (read-file-name "Add appointment to this diary file: ")))
1945 ;; Note: make-diary-entry will add a trailing blank char.... :(
1946 (make-diary-entry string non-marking diary-file
))))
1948 (provide 'icalendar
)
1950 ;; arch-tag: 74fdbe8e-0451-4e38-bb61-4416e822f4fc
1951 ;;; icalendar.el ends here