1 ;;; icalendar.el --- iCalendar implementation -*-coding: utf-8 -*-
3 ;; Copyright (C) 2002, 2003, 2004, 2005, 2006 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 ;; + check vcalendar version
77 ;; + check (unknown) elements
78 ;; + recurring events!
79 ;; + works for european style calendars only! Does it?
81 ;; + exceptions in recurring events
82 ;; + the parser is too soft
83 ;; + error log is incomplete
84 ;; + nice to have: #include "webcal://foo.com/some-calendar.ics"
85 ;; + timezones, currently all times are local!
87 ;; * Export from diary to ical
88 ;; + diary-date, diary-float, and self-made sexp entries are not
92 ;; + clean up all those date/time parsing functions
93 ;; + Handle todo items?
94 ;; + Check iso 8601 for datetime and period
95 ;; + Which chars to (un)escape?
100 (defconst icalendar-version
"0.13"
101 "Version number of icalendar.el.")
103 ;; ======================================================================
105 ;; ======================================================================
106 (defgroup icalendar nil
111 (defcustom icalendar-import-format
113 "Format string for importing events from iCalendar into Emacs diary.
114 This string defines how iCalendar events are inserted into diary
115 file. Meaning of the specifiers:
116 %c Class, see `icalendar-import-format-class'
117 %d Description, see `icalendar-import-format-description'
118 %l Location, see `icalendar-import-format-location'
119 %o Organizer, see `icalendar-import-format-organizer'
120 %s Summary, see `icalendar-import-format-summary'
121 %t Status, see `icalendar-import-format-status'
122 %u URL, see `icalendar-import-format-url'"
126 (defcustom icalendar-import-format-summary
128 "Format string defining how the summary element is formatted.
129 This applies only if the summary is not empty! `%s' is replaced
134 (defcustom icalendar-import-format-description
136 "Format string defining how the description element is formatted.
137 This applies only if the description is not empty! `%s' is
138 replaced by the description."
142 (defcustom icalendar-import-format-location
144 "Format string defining how the location element is formatted.
145 This applies only if the location is not empty! `%s' is replaced
150 (defcustom icalendar-import-format-organizer
152 "Format string defining how the organizer element is formatted.
153 This applies only if the organizer is not empty! `%s' is
154 replaced by the organizer."
158 (defcustom icalendar-import-format-url
160 "Format string defining how the URL element is formatted.
161 This applies only if the URL is not empty! `%s' is replaced by
166 (defcustom icalendar-import-format-status
168 "Format string defining how the status element is formatted.
169 This applies only if the status is not empty! `%s' is replaced by
174 (defcustom icalendar-import-format-class
176 "Format string defining how the class element is formatted.
177 This applies only if the class is not empty! `%s' is replaced by
182 (defvar icalendar-debug nil
183 "Enable icalendar debug messages.")
185 ;; ======================================================================
186 ;; NO USER SERVICABLE PARTS BELOW THIS LINE
187 ;; ======================================================================
189 (defconst icalendar--weekday-array
["SU" "MO" "TU" "WE" "TH" "FR" "SA"])
191 ;; ======================================================================
192 ;; all the other libs we need
193 ;; ======================================================================
196 ;; ======================================================================
198 ;; ======================================================================
199 (defun icalendar--dmsg (&rest args
)
200 "Print message ARGS if `icalendar-debug' is non-nil."
202 (apply 'message args
)))
204 ;; ======================================================================
205 ;; Core functionality
206 ;; Functions for parsing icalendars, importing and so on
207 ;; ======================================================================
209 (defun icalendar--get-unfolded-buffer (folded-ical-buffer)
210 "Return a new buffer containing the unfolded contents of a buffer.
211 Folding is the iCalendar way of wrapping long lines. In the
212 created buffer all occurrences of CR LF BLANK are replaced by the
213 empty string. Argument FOLDED-ICAL-BUFFER is the unfolded input
215 (let ((unfolded-buffer (get-buffer-create " *icalendar-work*")))
217 (set-buffer unfolded-buffer
)
219 (insert-buffer-substring folded-ical-buffer
)
220 (goto-char (point-min))
221 (while (re-search-forward "\r?\n[ \t]" nil t
)
222 (replace-match "" nil nil
)))
225 (defsubst icalendar--rris
(&rest args
)
226 "Replace regular expression in string.
227 Pass ARGS to `replace-regexp-in-string' (Emacs) or to
228 `replace-in-string' (XEmacs)."
230 (if (fboundp 'replace-in-string
)
231 (save-match-data ;; apparently XEmacs needs save-match-data
232 (apply 'replace-in-string args
))
234 (apply 'replace-regexp-in-string args
)))
236 (defun icalendar--read-element (invalue inparams
)
237 "Recursively read the next iCalendar element in the current buffer.
238 INVALUE gives the current iCalendar element we are reading.
239 INPARAMS gives the current parameters.....
240 This function calls itself recursively for each nested calendar element
242 (let (element children line name params param param-name param-value
247 (re-search-forward "^\\([A-Za-z0-9-]+\\)[;:]" nil t
))
248 (setq name
(intern (match-string 1)))
252 (while (looking-at ";")
253 (re-search-forward ";\\([A-Za-z0-9-]+\\)=" nil nil
)
254 (setq param-name
(intern (match-string 1)))
255 (re-search-forward "\\(\\([^;,:\"]+\\)\\|\"\\([^\"]+\\)\"\\)[;:]"
258 (setq param-value
(or (match-string 2) (match-string 3)))
259 (setq param
(list param-name param-value
))
260 (while (looking-at ",")
261 (re-search-forward "\\(\\([^;,:]+\\)\\|\"\\([^\"]+\\)\"\\)"
264 (setq param-value
(match-string 2))
265 (setq param-value
(match-string 3)))
266 (setq param
(append param param-value
)))
267 (setq params
(append params param
)))
268 (unless (looking-at ":")
271 (re-search-forward "\\(.*\\)\\(\r?\n[ \t].*\\)*" nil t
)
272 (setq value
(icalendar--rris "\r?\n[ \t]" "" (match-string 0)))
273 (setq line
(list name params value
))
274 (cond ((eq name
'BEGIN
)
277 (list (icalendar--read-element (intern value
)
282 (setq element
(append element
(list line
))))))
284 (list invalue inparams element children
)
287 ;; ======================================================================
288 ;; helper functions for examining events
289 ;; ======================================================================
291 ;;(defsubst icalendar--get-all-event-properties (event)
292 ;; "Return the list of properties in this EVENT."
293 ;; (car (cddr event)))
295 (defun icalendar--get-event-property (event prop
)
296 "For the given EVENT return the value of the first occurrence of PROP."
298 (let ((props (car (cddr event
))) pp
)
300 (setq pp
(car props
))
301 (if (eq (car pp
) prop
)
302 (throw 'found
(car (cddr pp
))))
303 (setq props
(cdr props
))))
306 (defun icalendar--get-event-property-attributes (event prop
)
307 "For the given EVENT return attributes of the first occurrence of PROP."
309 (let ((props (car (cddr event
))) pp
)
311 (setq pp
(car props
))
312 (if (eq (car pp
) prop
)
313 (throw 'found
(cadr pp
)))
314 (setq props
(cdr props
))))
317 (defun icalendar--get-event-properties (event prop
)
318 "For the given EVENT return a list of all values of the property PROP."
319 (let ((props (car (cddr event
))) pp result
)
321 (setq pp
(car props
))
322 (if (eq (car pp
) prop
)
323 (setq result
(append (split-string (car (cddr pp
)) ",") result
)))
324 (setq props
(cdr props
)))
327 ;; (defun icalendar--set-event-property (event prop new-value)
328 ;; "For the given EVENT set the property PROP to the value NEW-VALUE."
330 ;; (let ((props (car (cddr event))) pp)
332 ;; (setq pp (car props))
333 ;; (when (eq (car pp) prop)
334 ;; (setcdr (cdr pp) new-value)
335 ;; (throw 'found (car (cddr pp))))
336 ;; (setq props (cdr props)))
337 ;; (setq props (car (cddr event)))
338 ;; (setcar (cddr event)
339 ;; (append props (list (list prop nil new-value)))))))
341 (defun icalendar--get-children (node name
)
342 "Return all children of the given NODE which have a name NAME.
343 For instance the VCALENDAR node can have VEVENT children as well as VTODO
346 (children (cadr (cddr node
))))
347 (when (eq (car node
) name
)
349 ;;(message "%s" node)
354 (icalendar--get-children n name
))
358 (setq result
(append result subresult
))
359 (setq result subresult
)))))
363 (defun icalendar--all-events (icalendar)
364 "Return the list of all existing events in the given ICALENDAR."
365 (icalendar--get-children (car icalendar
) 'VEVENT
))
367 (defun icalendar--split-value (value-string)
368 "Split VALUE-STRING at ';='."
370 param-name param-value
)
373 (set-buffer (get-buffer-create " *icalendar-work*"))
374 (set-buffer-modified-p nil
)
376 (insert value-string
)
377 (goto-char (point-min))
380 "\\([A-Za-z0-9-]+\\)=\\(\\([^;,:]+\\)\\|\"\\([^\"]+\\)\"\\);?"
382 (setq param-name
(intern (match-string 1)))
383 (setq param-value
(match-string 2))
385 (append result
(list (list param-name param-value
)))))))
388 (defun icalendar--decode-isodatetime (isodatetimestring &optional day-shift
)
389 "Return ISODATETIMESTRING in format like `decode-time'.
390 Converts from ISO-8601 to Emacs representation. If
391 ISODATETIMESTRING specifies UTC time (trailing letter Z) the
392 decoded time is given in the local time zone! If optional
393 parameter DAY-SHIFT is non-nil the result is shifted by DAY-SHIFT
396 FIXME: TZID-attributes are ignored....!
397 FIXME: multiple comma-separated values should be allowed!"
398 (icalendar--dmsg isodatetimestring
)
399 (if isodatetimestring
400 ;; day/month/year must be present
401 (let ((year (read (substring isodatetimestring
0 4)))
402 (month (read (substring isodatetimestring
4 6)))
403 (day (read (substring isodatetimestring
6 8)))
407 (when (> (length isodatetimestring
) 12)
408 ;; hour/minute present
409 (setq hour
(read (substring isodatetimestring
9 11)))
410 (setq minute
(read (substring isodatetimestring
11 13))))
411 (when (> (length isodatetimestring
) 14)
413 (setq second
(read (substring isodatetimestring
13 15))))
414 (when (and (> (length isodatetimestring
) 15)
415 ;; UTC specifier present
416 (char-equal ?Z
(aref isodatetimestring
15)))
417 ;; if not UTC add current-time-zone offset
418 (setq second
(+ (car (current-time-zone)) second
)))
419 ;; shift if necessary
421 (let ((mdy (calendar-gregorian-from-absolute
422 (+ (calendar-absolute-from-gregorian
423 (list month day year
))
425 (setq month
(nth 0 mdy
))
426 (setq day
(nth 1 mdy
))
427 (setq year
(nth 2 mdy
))))
428 ;; create the decoded date-time
431 (decode-time (encode-time second minute hour day month year
))
433 (message "Cannot decode \"%s\"" isodatetimestring
)
434 ;; hope for the best...
435 (list second minute hour day month year
0 nil
0))))
436 ;; isodatetimestring == nil
439 (defun icalendar--decode-isoduration (isodurationstring
440 &optional duration-correction
)
441 "Convert ISODURATIONSTRING into format provided by `decode-time'.
442 Converts from ISO-8601 to Emacs representation. If ISODURATIONSTRING
443 specifies UTC time (trailing letter Z) the decoded time is given in
446 Optional argument DURATION-CORRECTION shortens result by one day.
448 FIXME: TZID-attributes are ignored....!
449 FIXME: multiple comma-separated values should be allowed!"
450 (if isodurationstring
455 "\\(\\([0-9]+\\)D\\)" ; days only
457 "\\(\\(\\([0-9]+\\)D\\)?T\\(\\([0-9]+\\)H\\)?" ; opt days
458 "\\(\\([0-9]+\\)M\\)?\\(\\([0-9]+\\)S\\)?\\)" ; mand. time
460 "\\(\\([0-9]+\\)W\\)" ; weeks only
461 "\\)$") isodurationstring
)
469 ((match-beginning 2) ;days only
470 (setq days
(read (substring isodurationstring
473 (when duration-correction
474 (setq days
(1- days
))))
475 ((match-beginning 4) ;days and time
476 (if (match-beginning 5)
477 (setq days
(* 7 (read (substring isodurationstring
480 (if (match-beginning 7)
481 (setq hours
(read (substring isodurationstring
484 (if (match-beginning 9)
485 (setq minutes
(read (substring isodurationstring
488 (if (match-beginning 11)
489 (setq seconds
(read (substring isodurationstring
492 ((match-beginning 13) ;weeks only
493 (setq days
(* 7 (read (substring isodurationstring
496 (list seconds minutes hours days months years
)))
497 ;; isodatetimestring == nil
500 (defun icalendar--add-decoded-times (time1 time2
)
502 Both times must be given in decoded form. One of these times must be
503 valid (year > 1900 or something)."
504 ;; FIXME: does this function exist already?
505 (decode-time (encode-time
506 (+ (nth 0 time1
) (nth 0 time2
))
507 (+ (nth 1 time1
) (nth 1 time2
))
508 (+ (nth 2 time1
) (nth 2 time2
))
509 (+ (nth 3 time1
) (nth 3 time2
))
510 (+ (nth 4 time1
) (nth 4 time2
))
511 (+ (nth 5 time1
) (nth 5 time2
))
514 ;;(or (nth 6 time1) (nth 6 time2)) ;; FIXME?
517 (defun icalendar--datetime-to-noneuropean-date (datetime &optional separator
)
518 "Convert the decoded DATETIME to non-european-style format.
519 Optional argument SEPARATOR gives the separator between month,
520 day, and year. If nil a blank character is used as separator.
521 Non-European format: \"month day year\"."
523 (format "%d%s%d%s%d" (nth 4 datetime
) ;month
525 (nth 3 datetime
) ;day
527 (nth 5 datetime
)) ;year
531 (defun icalendar--datetime-to-european-date (datetime &optional separator
)
532 "Convert the decoded DATETIME to European format.
533 Optional argument SEPARATOR gives the separator between month,
534 day, and year. If nil a blank character is used as separator.
535 European format: (day month year).
538 (format "%d%s%d%s%d" (nth 3 datetime
) ;day
540 (nth 4 datetime
) ;month
542 (nth 5 datetime
)) ;year
546 (defun icalendar--datetime-to-diary-date (datetime &optional separator
)
547 "Convert the decoded DATETIME to diary format.
548 Optional argument SEPARATOR gives the separator between month,
549 day, and year. If nil a blank character is used as separator.
550 Call icalendar--datetime-to-(non)-european-date according to
551 value of `european-calendar-style'."
552 (if european-calendar-style
553 (icalendar--datetime-to-european-date datetime separator
)
554 (icalendar--datetime-to-noneuropean-date datetime separator
)))
556 (defun icalendar--datetime-to-colontime (datetime)
557 "Extract the time part of a decoded DATETIME into 24-hour format.
558 Note that this silently ignores seconds."
559 (format "%02d:%02d" (nth 2 datetime
) (nth 1 datetime
)))
561 (defun icalendar--get-month-number (monthname)
562 "Return the month number for the given MONTHNAME."
565 (m (downcase monthname
)))
566 (mapc (lambda (month)
567 (let ((mm (downcase month
)))
568 (if (or (string-equal mm m
)
569 (string-equal (substring mm
0 3) m
))
571 (setq num
(1+ num
))))
572 calendar-month-name-array
))
576 (defun icalendar--get-weekday-number (abbrevweekday)
577 "Return the number for the ABBREVWEEKDAY."
581 (aw (downcase abbrevweekday
)))
583 (let ((d (downcase day
)))
584 (if (string-equal d aw
)
586 (setq num
(1+ num
))))
587 icalendar--weekday-array
)))
591 (defun icalendar--get-weekday-abbrev (weekday)
592 "Return the abbreviated WEEKDAY."
595 (w (downcase weekday
)))
597 (let ((d (downcase day
)))
598 (if (or (string-equal d w
)
599 (string-equal (substring d
0 3) w
))
600 (throw 'found
(aref icalendar--weekday-array num
)))
601 (setq num
(1+ num
))))
602 calendar-day-name-array
))
606 (defun icalendar--date-to-isodate (date &optional day-shift
)
607 "Convert DATE to iso-style date.
608 DATE must be a list of the form (month day year).
609 If DAY-SHIFT is non-nil, the result is shifted by DAY-SHIFT days."
610 (let ((mdy (calendar-gregorian-from-absolute
611 (+ (calendar-absolute-from-gregorian date
)
613 (format "%04d%02d%02d" (nth 2 mdy
) (nth 0 mdy
) (nth 1 mdy
))))
616 (defun icalendar--datestring-to-isodate (datestring &optional day-shift
)
617 "Convert diary-style DATESTRING to iso-style date.
618 If DAY-SHIFT is non-nil, the result is shifted by DAY-SHIFT days
619 -- DAY-SHIFT must be either nil or an integer. This function
620 takes care of european-style."
621 (let ((day -
1) month year
)
623 (cond ( ;; numeric date
624 (string-match (concat "\\s-*"
625 "0?\\([1-9][0-9]?\\)[ \t/]\\s-*"
626 "0?\\([1-9][0-9]?\\),?[ \t/]\\s-*"
627 "\\([0-9]\\{4\\}\\)")
629 (setq day
(read (substring datestring
(match-beginning 1)
631 (setq month
(read (substring datestring
(match-beginning 2)
633 (setq year
(read (substring datestring
(match-beginning 3)
635 (unless european-calendar-style
639 ( ;; date contains month names -- european-style
640 (string-match (concat "\\s-*"
641 "0?\\([123]?[0-9]\\)[ \t/]\\s-*"
642 "\\([A-Za-z][^ ]+\\)[ \t/]\\s-*"
643 "\\([0-9]\\{4\\}\\)")
645 (setq day
(read (substring datestring
(match-beginning 1)
647 (setq month
(icalendar--get-month-number
648 (substring datestring
(match-beginning 2)
650 (setq year
(read (substring datestring
(match-beginning 3)
652 ( ;; date contains month names -- non-european-style
653 (string-match (concat "\\s-*"
654 "\\([A-Za-z][^ ]+\\)[ \t/]\\s-*"
655 "0?\\([123]?[0-9]\\),?[ \t/]\\s-*"
656 "\\([0-9]\\{4\\}\\)")
658 (setq day
(read (substring datestring
(match-beginning 2)
660 (setq month
(icalendar--get-month-number
661 (substring datestring
(match-beginning 1)
663 (setq year
(read (substring datestring
(match-beginning 3)
668 (let ((mdy (calendar-gregorian-from-absolute
669 (+ (calendar-absolute-from-gregorian (list month day
672 (format "%04d%02d%02d" (nth 2 mdy
) (nth 0 mdy
) (nth 1 mdy
)))
675 (defun icalendar--diarytime-to-isotime (timestring ampmstring
)
676 "Convert a a time like 9:30pm to an iso-conform string like T213000.
677 In this example the TIMESTRING would be \"9:30\" and the AMPMSTRING
680 (let ((starttimenum (read (icalendar--rris ":" "" timestring
))))
681 ;; take care of am/pm style
682 (if (and ampmstring
(string= "pm" ampmstring
))
683 (setq starttimenum
(+ starttimenum
1200)))
684 (format "T%04d00" starttimenum
))
687 (defun icalendar--convert-string-for-export (string)
688 "Escape comma and other critical characters in STRING."
689 (icalendar--rris "," "\\\\," string
))
691 (defun icalendar--convert-string-for-import (string)
692 "Remove escape chars for comma, semicolon etc. from STRING."
694 "\\\\n" "\n " (icalendar--rris
695 "\\\\\"" "\"" (icalendar--rris
696 "\\\\;" ";" (icalendar--rris
697 "\\\\," "," string
)))))
699 ;; ======================================================================
700 ;; Export -- convert emacs-diary to icalendar
701 ;; ======================================================================
704 (defun icalendar-export-file (diary-filename ical-filename
)
705 "Export diary file to iCalendar format.
706 All diary entries in the file DIARY-FILENAME are converted to iCalendar
707 format. The result is appended to the file ICAL-FILENAME."
708 (interactive "FExport diary data from file:
709 Finto iCalendar file: ")
711 (set-buffer (find-file diary-filename
))
712 (icalendar-export-region (point-min) (point-max) ical-filename
)))
714 (defalias 'icalendar-convert-diary-to-ical
'icalendar-export-file
)
715 (make-obsolete 'icalendar-convert-diary-to-ical
'icalendar-export-file
)
718 (defun icalendar-export-region (min max ical-filename
)
719 "Export region in diary file to iCalendar format.
720 All diary entries in the region from MIN to MAX in the current buffer are
721 converted to iCalendar format. The result is appended to the file
723 This function attempts to return t if something goes wrong. In this
724 case an error string which describes all the errors and problems is
725 written into the buffer `*icalendar-errors*'."
727 FExport diary data into iCalendar file: ")
736 (nonmarker (concat "^" (regexp-quote diary-nonmarking-symbol
)
738 (other-elements nil
))
739 ;; prepare buffer with error messages
741 (set-buffer (get-buffer-create "*icalendar-errors*"))
747 (while (re-search-forward
748 "^\\([^ \t\n].+\\)\\(\\(\n[ \t].*\\)*\\)" max t
)
749 (setq entry-main
(match-string 1))
750 (if (match-beginning 2)
751 (setq entry-rest
(match-string 2))
752 (setq entry-rest
""))
753 (setq header
(format "\nBEGIN:VEVENT\nUID:emacs%d%d%d"
755 (cadr (current-time))
756 (car (cddr (current-time)))))
757 (condition-case error-val
759 (setq contents-n-summary
760 (icalendar--convert-to-ical nonmarker entry-main
))
761 (setq other-elements
(icalendar--parse-summary-and-rest
762 (concat entry-main entry-rest
)))
763 (setq contents
(concat (car contents-n-summary
)
764 "\nSUMMARY:" (cadr contents-n-summary
)))
765 (let ((cla (cdr (assoc 'cla other-elements
)))
766 (des (cdr (assoc 'des other-elements
)))
767 (loc (cdr (assoc 'loc other-elements
)))
768 (org (cdr (assoc 'org other-elements
)))
769 (sta (cdr (assoc 'sta other-elements
)))
770 (sum (cdr (assoc 'sum other-elements
)))
771 (url (cdr (assoc 'url other-elements
))))
773 (setq contents
(concat contents
"\nCLASS:" cla
)))
775 (setq contents
(concat contents
"\nDESCRIPTION:" des
)))
777 (setq contents
(concat contents
"\nLOCATION:" loc
)))
779 (setq contents
(concat contents
"\nORGANIZER:" org
)))
781 (setq contents
(concat contents
"\nSTATUS:" sta
)))
783 ;; (setq contents (concat contents "\nSUMMARY:" sum)))
785 (setq contents
(concat contents
"\nURL:" url
))))
786 (setq result
(concat result header contents
"\nEND:VEVENT")))
791 (set-buffer (get-buffer-create "*icalendar-errors*"))
792 (insert (format "Error in line %d -- %s: `%s'\n"
793 (count-lines (point-min) (point))
797 ;; we're done, insert everything into the file
799 (let ((coding-system-for-write 'utf-8
))
800 (set-buffer (find-file ical-filename
))
801 (goto-char (point-max))
802 (insert "BEGIN:VCALENDAR")
803 (insert "\nPRODID:-//Emacs//NONSGML icalendar.el//EN")
804 (insert "\nVERSION:2.0")
806 (insert "\nEND:VCALENDAR\n")
807 ;; save the diary file
813 (defun icalendar--convert-to-ical (nonmarker entry-main
)
814 "Convert a diary entry to icalendar format.
815 NONMARKER is a regular expression matching the start of non-marking
816 entries. ENTRY-MAIN is the first line of the diary entry."
818 ;; anniversaries -- %%(diary-anniversary ...)
819 (icalendar--convert-anniversary-to-ical nonmarker entry-main
)
820 ;; cyclic events -- %%(diary-cyclic ...)
821 (icalendar--convert-cyclic-to-ical nonmarker entry-main
)
822 ;; diary-date -- %%(diary-date ...)
823 (icalendar--convert-date-to-ical nonmarker entry-main
)
824 ;; float events -- %%(diary-float ...)
825 (icalendar--convert-float-to-ical nonmarker entry-main
)
826 ;; block events -- %%(diary-block ...)
827 (icalendar--convert-block-to-ical nonmarker entry-main
)
828 ;; other sexp diary entries
829 (icalendar--convert-sexp-to-ical nonmarker entry-main
)
830 ;; weekly by day -- Monday 8:30 Team meeting
831 (icalendar--convert-weekly-to-ical nonmarker entry-main
)
832 ;; yearly by day -- 1 May Tag der Arbeit
833 (icalendar--convert-yearly-to-ical nonmarker entry-main
)
834 ;; "ordinary" events, start and end time given
836 (icalendar--convert-ordinary-to-ical nonmarker entry-main
)
838 ;; Oops! what's that?
839 (error "Could not parse entry")))
841 (defun icalendar--parse-summary-and-rest (summary-and-rest)
842 "Parse SUMMARY-AND-REST from a diary to fill iCalendar properties."
844 (let* ((s icalendar-import-format
)
845 (p-cla (or (string-match "%c" icalendar-import-format
) -
1))
846 (p-des (or (string-match "%d" icalendar-import-format
) -
1))
847 (p-loc (or (string-match "%l" icalendar-import-format
) -
1))
848 (p-org (or (string-match "%o" icalendar-import-format
) -
1))
849 (p-sum (or (string-match "%s" icalendar-import-format
) -
1))
850 (p-sta (or (string-match "%t" icalendar-import-format
) -
1))
851 (p-url (or (string-match "%u" icalendar-import-format
) -
1))
852 (p-list (sort (list p-cla p-des p-loc p-org p-sta p-sum p-url
) '<))
853 pos-cla pos-des pos-loc pos-org pos-sta pos-sum pos-url
)
854 (dotimes (i (length p-list
))
855 (cond ((and (>= p-cla
0) (= (nth i p-list
) p-cla
))
856 (setq pos-cla
(+ 2 (* 2 i
))))
857 ((and (>= p-des
0) (= (nth i p-list
) p-des
))
858 (setq pos-des
(+ 2 (* 2 i
))))
859 ((and (>= p-loc
0) (= (nth i p-list
) p-loc
))
860 (setq pos-loc
(+ 2 (* 2 i
))))
861 ((and (>= p-org
0) (= (nth i p-list
) p-org
))
862 (setq pos-org
(+ 2 (* 2 i
))))
863 ((and (>= p-sta
0) (= (nth i p-list
) p-sta
))
864 (setq pos-sta
(+ 2 (* 2 i
))))
865 ((and (>= p-sum
0) (= (nth i p-list
) p-sum
))
866 (setq pos-sum
(+ 2 (* 2 i
))))
867 ((and (>= p-url
0) (= (nth i p-list
) p-url
))
868 (setq pos-url
(+ 2 (* 2 i
))))))
870 (setq s
(icalendar--rris (car ij
) (cadr ij
) s t t
)))
872 ;; summary must be first! because of %s
874 (concat "\\(" icalendar-import-format-summary
"\\)?"))
876 (concat "\\(" icalendar-import-format-class
"\\)?"))
878 (concat "\\(" icalendar-import-format-description
"\\)?"))
880 (concat "\\(" icalendar-import-format-location
"\\)?"))
882 (concat "\\(" icalendar-import-format-organizer
"\\)?"))
884 (concat "\\(" icalendar-import-format-status
"\\)?"))
886 (concat "\\(" icalendar-import-format-url
"\\)?"))))
887 (setq s
(concat (icalendar--rris "%s" "\\(.*\\)" s nil t
) " "))
888 (if (string-match s summary-and-rest
)
889 (let (cla des loc org sta sum url
)
890 (if (and pos-sum
(match-beginning pos-sum
))
891 (setq sum
(substring summary-and-rest
892 (match-beginning pos-sum
)
893 (match-end pos-sum
))))
894 (if (and pos-cla
(match-beginning pos-cla
))
895 (setq cla
(substring summary-and-rest
896 (match-beginning pos-cla
)
897 (match-end pos-cla
))))
898 (if (and pos-des
(match-beginning pos-des
))
899 (setq des
(substring summary-and-rest
900 (match-beginning pos-des
)
901 (match-end pos-des
))))
902 (if (and pos-loc
(match-beginning pos-loc
))
903 (setq loc
(substring summary-and-rest
904 (match-beginning pos-loc
)
905 (match-end pos-loc
))))
906 (if (and pos-org
(match-beginning pos-org
))
907 (setq org
(substring summary-and-rest
908 (match-beginning pos-org
)
909 (match-end pos-org
))))
910 (if (and pos-sta
(match-beginning pos-sta
))
911 (setq sta
(substring summary-and-rest
912 (match-beginning pos-sta
)
913 (match-end pos-sta
))))
914 (if (and pos-url
(match-beginning pos-url
))
915 (setq url
(substring summary-and-rest
916 (match-beginning pos-url
)
917 (match-end pos-url
))))
918 (list (if cla
(cons 'cla cla
) nil
)
919 (if des
(cons 'des des
) nil
)
920 (if loc
(cons 'loc loc
) nil
)
921 (if org
(cons 'org org
) nil
)
922 (if sta
(cons 'sta sta
) nil
)
923 ;;(if sum (cons 'sum sum) nil)
924 (if url
(cons 'url url
) nil
)))))))
926 ;; subroutines for icalendar-export-region
927 (defun icalendar--convert-ordinary-to-ical (nonmarker entry-main
)
928 "Convert \"ordinary\" diary entry to icalendar format.
929 NONMARKER is a regular expression matching the start of non-marking
930 entries. ENTRY-MAIN is the first line of the diary entry."
931 (if (string-match (concat nonmarker
932 "\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\)\\s-*"
933 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
935 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
939 (let* ((datetime (substring entry-main
(match-beginning 1)
941 (startisostring (icalendar--datestring-to-isodate
943 (endisostring (icalendar--datestring-to-isodate
945 (starttimestring (icalendar--diarytime-to-isotime
946 (if (match-beginning 3)
947 (substring entry-main
951 (if (match-beginning 4)
952 (substring entry-main
956 (endtimestring (icalendar--diarytime-to-isotime
957 (if (match-beginning 6)
958 (substring entry-main
962 (if (match-beginning 7)
963 (substring entry-main
967 (summary (icalendar--convert-string-for-export
968 (substring entry-main
(match-beginning 8)
970 (icalendar--dmsg "ordinary %s" entry-main
)
972 (unless startisostring
973 (error "Could not parse date"))
974 (when starttimestring
975 (unless endtimestring
977 (read (icalendar--rris "^T0?" ""
979 (setq endtimestring
(format "T%06d"
981 (list (concat "\nDTSTART;"
982 (if starttimestring
"VALUE=DATE-TIME:"
985 (or starttimestring
"")
987 (if endtimestring
"VALUE=DATE-TIME:"
992 (or endtimestring
""))
997 (defun icalendar--convert-weekly-to-ical (nonmarker entry-main
)
998 "Convert weekly diary entry to icalendar format.
999 NONMARKER is a regular expression matching the start of non-marking
1000 entries. ENTRY-MAIN is the first line of the diary entry."
1001 (if (and (string-match (concat nonmarker
1003 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)"
1006 "\\([1-9][0-9]?:[0-9][0-9]\\)"
1009 "\\s-*\\(.*?\\) ?$")
1011 (icalendar--get-weekday-abbrev
1012 (substring entry-main
(match-beginning 1)
1014 (let* ((day (icalendar--get-weekday-abbrev
1015 (substring entry-main
(match-beginning 1)
1017 (starttimestring (icalendar--diarytime-to-isotime
1018 (if (match-beginning 3)
1019 (substring entry-main
1023 (if (match-beginning 4)
1024 (substring entry-main
1028 (endtimestring (icalendar--diarytime-to-isotime
1029 (if (match-beginning 6)
1030 (substring entry-main
1034 (if (match-beginning 7)
1035 (substring entry-main
1039 (summary (icalendar--convert-string-for-export
1040 (substring entry-main
(match-beginning 8)
1042 (icalendar--dmsg "weekly %s" entry-main
)
1044 (when starttimestring
1045 (unless endtimestring
1047 (icalendar--rris "^T0?" ""
1049 (setq endtimestring
(format "T%06d"
1051 (list (concat "\nDTSTART;"
1055 ;; find the correct week day,
1056 ;; 1st january 2000 was a saturday
1059 (+ (icalendar--get-weekday-number day
) 2))
1060 (or starttimestring
"")
1067 ;; end is non-inclusive!
1068 (+ (icalendar--get-weekday-number day
)
1069 (if endtimestring
2 3)))
1070 (or endtimestring
"")
1071 "\nRRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY="
1077 (defun icalendar--convert-yearly-to-ical (nonmarker entry-main
)
1078 "Convert yearly diary entry to icalendar format.
1079 NONMARKER is a regular expression matching the start of non-marking
1080 entries. ENTRY-MAIN is the first line of the diary entry."
1081 (if (string-match (concat nonmarker
1082 (if european-calendar-style
1083 "0?\\([1-9]+[0-9]?\\)\\s-+\\([a-z]+\\)\\s-+"
1084 "\\([a-z]+\\)\\s-+0?\\([1-9]+[0-9]?\\)\\s-+")
1086 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
1088 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1090 "\\s-*\\([^0-9]+.*?\\) ?$" ; must not match years
1093 (let* ((daypos (if european-calendar-style
1 2))
1094 (monpos (if european-calendar-style
2 1))
1095 (day (read (substring entry-main
1096 (match-beginning daypos
)
1097 (match-end daypos
))))
1098 (month (icalendar--get-month-number
1099 (substring entry-main
1100 (match-beginning monpos
)
1101 (match-end monpos
))))
1102 (starttimestring (icalendar--diarytime-to-isotime
1103 (if (match-beginning 4)
1104 (substring entry-main
1108 (if (match-beginning 5)
1109 (substring entry-main
1113 (endtimestring (icalendar--diarytime-to-isotime
1114 (if (match-beginning 7)
1115 (substring entry-main
1119 (if (match-beginning 8)
1120 (substring entry-main
1124 (summary (icalendar--convert-string-for-export
1125 (substring entry-main
(match-beginning 9)
1127 (icalendar--dmsg "yearly %s" entry-main
)
1129 (when starttimestring
1130 (unless endtimestring
1132 (icalendar--rris "^T0?" ""
1134 (setq endtimestring
(format "T%06d"
1136 (list (concat "\nDTSTART;"
1137 (if starttimestring
"VALUE=DATE-TIME:"
1139 (format "1900%02d%02d" month day
)
1140 (or starttimestring
"")
1142 (if endtimestring
"VALUE=DATE-TIME:"
1144 ;; end is not included! shift by one day
1145 (icalendar--date-to-isodate
1146 (list month day
1900)
1147 (if endtimestring
0 1))
1148 (or endtimestring
"")
1149 "\nRRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH="
1150 (format "%2d" month
)
1157 (defun icalendar--convert-sexp-to-ical (nonmarker entry-main
)
1158 "Convert complex sexp diary entry to icalendar format -- unsupported!
1162 NONMARKER is a regular expression matching the start of non-marking
1163 entries. ENTRY-MAIN is the first line of the diary entry."
1164 (cond ((string-match (concat nonmarker
1165 "%%(and \\(([^)]+)\\))\\(\\s-*.*?\\) ?$")
1167 ;; simple sexp entry as generated by icalendar.el: strip off the
1168 ;; unnecessary (and)
1169 (icalendar--dmsg "diary-sexp from icalendar.el %s" entry-main
)
1170 (icalendar--convert-to-ical
1173 (substring entry-main
(match-beginning 1) (match-end 1))
1174 (substring entry-main
(match-beginning 2) (match-end 2)))))
1175 ((string-match (concat nonmarker
1178 (icalendar--dmsg "diary-sexp %s" entry-main
)
1179 (error "Sexp-entries are not supported yet"))
1184 (defun icalendar--convert-block-to-ical (nonmarker entry-main
)
1185 "Convert block diary entry to icalendar format.
1186 NONMARKER is a regular expression matching the start of non-marking
1187 entries. ENTRY-MAIN is the first line of the diary entry."
1188 (if (string-match (concat nonmarker
1189 "%%(diary-block \\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\)"
1190 " +\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\))\\s-*"
1191 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
1193 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1195 "\\s-*\\(.*?\\) ?$")
1197 (let* ((startstring (substring entry-main
1200 (endstring (substring entry-main
1203 (startisostring (icalendar--datestring-to-isodate
1205 (endisostring (icalendar--datestring-to-isodate
1207 (endisostring+1 (icalendar--datestring-to-isodate
1209 (starttimestring (icalendar--diarytime-to-isotime
1210 (if (match-beginning 4)
1211 (substring entry-main
1215 (if (match-beginning 5)
1216 (substring entry-main
1220 (endtimestring (icalendar--diarytime-to-isotime
1221 (if (match-beginning 7)
1222 (substring entry-main
1226 (if (match-beginning 8)
1227 (substring entry-main
1231 (summary (icalendar--convert-string-for-export
1232 (substring entry-main
(match-beginning 9)
1234 (icalendar--dmsg "diary-block %s" entry-main
)
1235 (when starttimestring
1236 (unless endtimestring
1238 (read (icalendar--rris "^T0?" ""
1240 (setq endtimestring
(format "T%06d"
1243 ;; with time -> write rrule
1244 (list (concat "\nDTSTART;VALUE=DATE-TIME:"
1247 "\nDTEND;VALUE=DATE-TIME:"
1250 "\nRRULE:FREQ=DAILY;INTERVAL=1;UNTIL="
1253 ;; no time -> write long event
1254 (list (concat "\nDTSTART;VALUE=DATE:" startisostring
1255 "\nDTEND;VALUE=DATE:" endisostring
+1)
1260 (defun icalendar--convert-float-to-ical (nonmarker entry-main
)
1261 "Convert float diary entry to icalendar format -- unsupported!
1265 NONMARKER is a regular expression matching the start of non-marking
1266 entries. ENTRY-MAIN is the first line of the diary entry."
1267 (if (string-match (concat nonmarker
1268 "%%(diary-float \\([^)]+\\))\\s-*\\(.*?\\) ?$")
1271 (icalendar--dmsg "diary-float %s" entry-main
)
1272 (error "`diary-float' is not supported yet"))
1276 (defun icalendar--convert-date-to-ical (nonmarker entry-main
)
1277 "Convert `diary-date' diary entry to icalendar format -- unsupported!
1281 NONMARKER is a regular expression matching the start of non-marking
1282 entries. ENTRY-MAIN is the first line of the diary entry."
1283 (if (string-match (concat nonmarker
1284 "%%(diary-date \\([^)]+\\))\\s-*\\(.*?\\) ?$")
1287 (icalendar--dmsg "diary-date %s" entry-main
)
1288 (error "`diary-date' is not supported yet"))
1292 (defun icalendar--convert-cyclic-to-ical (nonmarker entry-main
)
1293 "Convert `diary-cyclic' diary entry to icalendar format.
1294 NONMARKER is a regular expression matching the start of non-marking
1295 entries. ENTRY-MAIN is the first line of the diary entry."
1296 (if (string-match (concat nonmarker
1297 "%%(diary-cyclic \\([^ ]+\\) +"
1298 "\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\))\\s-*"
1299 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
1301 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1303 "\\s-*\\(.*?\\) ?$")
1305 (let* ((frequency (substring entry-main
(match-beginning 1)
1307 (datetime (substring entry-main
(match-beginning 2)
1309 (startisostring (icalendar--datestring-to-isodate
1311 (endisostring (icalendar--datestring-to-isodate
1313 (endisostring+1 (icalendar--datestring-to-isodate
1315 (starttimestring (icalendar--diarytime-to-isotime
1316 (if (match-beginning 4)
1317 (substring entry-main
1321 (if (match-beginning 5)
1322 (substring entry-main
1326 (endtimestring (icalendar--diarytime-to-isotime
1327 (if (match-beginning 7)
1328 (substring entry-main
1332 (if (match-beginning 8)
1333 (substring entry-main
1337 (summary (icalendar--convert-string-for-export
1338 (substring entry-main
(match-beginning 9)
1340 (icalendar--dmsg "diary-cyclic %s" entry-main
)
1341 (when starttimestring
1342 (unless endtimestring
1344 (read (icalendar--rris "^T0?" ""
1346 (setq endtimestring
(format "T%06d"
1348 (list (concat "\nDTSTART;"
1349 (if starttimestring
"VALUE=DATE-TIME:"
1352 (or starttimestring
"")
1354 (if endtimestring
"VALUE=DATE-TIME:"
1356 (if endtimestring endisostring endisostring
+1)
1357 (or endtimestring
"")
1358 "\nRRULE:FREQ=DAILY;INTERVAL=" frequency
1359 ;; strange: korganizer does not expect
1360 ;; BYSOMETHING here...
1366 (defun icalendar--convert-anniversary-to-ical (nonmarker entry-main
)
1367 "Convert `diary-anniversary' diary entry to icalendar format.
1368 NONMARKER is a regular expression matching the start of non-marking
1369 entries. ENTRY-MAIN is the first line of the diary entry."
1370 (if (string-match (concat nonmarker
1371 "%%(diary-anniversary \\([^)]+\\))\\s-*"
1372 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
1374 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1376 "\\s-*\\(.*?\\) ?$")
1378 (let* ((datetime (substring entry-main
(match-beginning 1)
1380 (startisostring (icalendar--datestring-to-isodate
1382 (endisostring (icalendar--datestring-to-isodate
1384 (starttimestring (icalendar--diarytime-to-isotime
1385 (if (match-beginning 3)
1386 (substring entry-main
1390 (if (match-beginning 4)
1391 (substring entry-main
1395 (endtimestring (icalendar--diarytime-to-isotime
1396 (if (match-beginning 6)
1397 (substring entry-main
1401 (if (match-beginning 7)
1402 (substring entry-main
1406 (summary (icalendar--convert-string-for-export
1407 (substring entry-main
(match-beginning 8)
1409 (icalendar--dmsg "diary-anniversary %s" entry-main
)
1410 (when starttimestring
1411 (unless endtimestring
1413 (read (icalendar--rris "^T0?" ""
1415 (setq endtimestring
(format "T%06d"
1417 (list (concat "\nDTSTART;"
1418 (if starttimestring
"VALUE=DATE-TIME:"
1421 (or starttimestring
"")
1423 (if endtimestring
"VALUE=DATE-TIME:"
1426 (or endtimestring
"")
1427 "\nRRULE:FREQ=YEARLY;INTERVAL=1"
1428 ;; the following is redundant,
1429 ;; but korganizer seems to expect this... ;(
1430 ;; and evolution doesn't understand it... :(
1431 ;; so... who is wrong?!
1433 (substring startisostring
4 6)
1435 (substring startisostring
6 8))
1440 ;; ======================================================================
1441 ;; Import -- convert icalendar to emacs-diary
1442 ;; ======================================================================
1445 (defun icalendar-import-file (ical-filename diary-filename
1446 &optional non-marking
)
1447 "Import an iCalendar file and append to a diary file.
1448 Argument ICAL-FILENAME output iCalendar file.
1449 Argument DIARY-FILENAME input `diary-file'.
1450 Optional argument NON-MARKING determines whether events are created as
1451 non-marking or not."
1452 (interactive "fImport iCalendar data from file:
1455 ;; clean up the diary file
1456 (save-current-buffer
1457 ;; now load and convert from the ical file
1458 (set-buffer (find-file ical-filename
))
1459 (icalendar-import-buffer diary-filename t non-marking
)))
1462 (defun icalendar-import-buffer (&optional diary-file do-not-ask
1464 "Extract iCalendar events from current buffer.
1466 This function searches the current buffer for the first iCalendar
1467 object, reads it and adds all VEVENT elements to the diary
1470 It will ask for each appointment whether to add it to the diary
1471 when DO-NOT-ASK is non-nil. When called interactively,
1472 DO-NOT-ASK is set to t, so that you are asked fore each event.
1474 NON-MARKING determines whether diary events are created as
1477 Return code t means that importing worked well, return code nil
1478 means that an error has occured. Error messages will be in the
1479 buffer `*icalendar-errors*'."
1481 (save-current-buffer
1483 (message "Preparing icalendar...")
1484 (set-buffer (icalendar--get-unfolded-buffer (current-buffer)))
1485 (goto-char (point-min))
1486 (message "Preparing icalendar...done")
1487 (if (re-search-forward "^BEGIN:VCALENDAR\\s-*$" nil t
)
1488 (let (ical-contents ical-errors
)
1490 (message "Reading icalendar...")
1492 (setq ical-contents
(icalendar--read-element nil nil
))
1493 (message "Reading icalendar...done")
1495 (message "Converting icalendar...")
1496 (setq ical-errors
(icalendar--convert-ical-to-diary
1498 diary-file do-not-ask non-marking
))
1500 ;; save the diary file if it is visited already
1501 (let ((b (find-buffer-visiting diary-file
)))
1503 (save-current-buffer
1506 (message "Converting icalendar...done")
1507 ;; return t if no error occured
1510 "Current buffer does not contain icalendar contents!")
1511 ;; return nil, i.e. import did not work
1514 (defalias 'icalendar-extract-ical-from-buffer
'icalendar-import-buffer
)
1515 (make-obsolete 'icalendar-extract-ical-from-buffer
'icalendar-import-buffer
)
1517 (defun icalendar--format-ical-event (event)
1518 "Create a string representation of an iCalendar EVENT."
1519 (let ((string icalendar-import-format
)
1521 '(("%c" CLASS icalendar-import-format-class
)
1522 ("%d" DESCRIPTION icalendar-import-format-description
)
1523 ("%l" LOCATION icalendar-import-format-location
)
1524 ("%o" ORGANIZER icalendar-import-format-organizer
)
1525 ("%s" SUMMARY icalendar-import-format-summary
)
1526 ("%t" STATUS icalendar-import-format-status
)
1527 ("%u" URL icalendar-import-format-url
))))
1528 ;; convert the specifiers in the format string
1530 (let* ((spec (car i
))
1532 (format (car (cddr i
)))
1533 (contents (icalendar--get-event-property event prop
))
1534 (formatted-contents ""))
1535 (when (and contents
(> (length contents
) 0))
1536 (setq formatted-contents
1537 (icalendar--rris "%s"
1538 (icalendar--convert-string-for-import
1540 (symbol-value format
)
1542 (setq string
(icalendar--rris spec
1549 (defun icalendar--convert-ical-to-diary (ical-list diary-file
1550 &optional do-not-ask
1552 "Convert Calendar data to an Emacs diary file.
1553 Import VEVENTS from the iCalendar object ICAL-LIST and saves them to a
1554 DIARY-FILE. If DO-NOT-ASK is nil the user is asked for each event
1555 whether to actually import it. NON-MARKING determines whether diary
1556 events are created as non-marking.
1557 This function attempts to return t if something goes wrong. In this
1558 case an error string which describes all the errors and problems is
1559 written into the buffer `*icalendar-errors*'."
1560 (let* ((ev (icalendar--all-events ical-list
))
1565 ;; step through all events/appointments
1570 (condition-case error-val
1571 (let* ((dtstart (icalendar--get-event-property e
'DTSTART
))
1572 (dtstart-dec (icalendar--decode-isodatetime dtstart
))
1573 (start-d (icalendar--datetime-to-diary-date
1575 (start-t (icalendar--datetime-to-colontime dtstart-dec
))
1576 (dtend (icalendar--get-event-property e
'DTEND
))
1577 (dtend-dec (icalendar--decode-isodatetime dtend
))
1578 (dtend-1-dec (icalendar--decode-isodatetime dtend -
1))
1582 (summary (icalendar--convert-string-for-import
1583 (or (icalendar--get-event-property e
'SUMMARY
)
1585 (rrule (icalendar--get-event-property e
'RRULE
))
1586 (rdate (icalendar--get-event-property e
'RDATE
))
1587 (duration (icalendar--get-event-property e
'DURATION
)))
1588 (icalendar--dmsg "%s: `%s'" start-d summary
)
1589 ;; check whether start-time is missing
1592 (cadr (icalendar--get-event-property-attributes
1597 (let ((dtend-dec-d (icalendar--add-decoded-times
1599 (icalendar--decode-isoduration duration
)))
1600 (dtend-1-dec-d (icalendar--add-decoded-times
1602 (icalendar--decode-isoduration duration
1604 (if (and dtend-dec
(not (eq dtend-dec dtend-dec-d
)))
1605 (message "Inconsistent endtime and duration for %s"
1607 (setq dtend-dec dtend-dec-d
)
1608 (setq dtend-1-dec dtend-1-dec-d
)))
1609 (setq end-d
(if dtend-dec
1610 (icalendar--datetime-to-diary-date dtend-dec
)
1612 (setq end-1-d
(if dtend-1-dec
1613 (icalendar--datetime-to-diary-date dtend-1-dec
)
1615 (setq end-t
(if (and
1619 (icalendar--get-event-property-attributes
1622 (icalendar--datetime-to-colontime dtend-dec
)
1624 (icalendar--dmsg "start-d: %s, end-d: %s" start-d end-d
)
1629 (icalendar--convert-recurring-to-diary e dtstart-dec start-t
1633 (icalendar--dmsg "rdate event")
1634 (setq diary-string
"")
1635 (mapcar (lambda (datestring)
1637 (concat diary-string
1638 (format "......"))))
1639 (icalendar--split-value rdate
)))
1640 ;; non-recurring event
1642 ((not (string= start-d end-d
))
1644 (icalendar--convert-non-recurring-all-day-to-diary
1648 ((and start-t
(or (not end-t
)
1649 (not (string= start-t end-t
))))
1651 (icalendar--convert-non-recurring-not-all-day-to-diary
1652 e dtstart-dec dtend-dec start-t end-t
))
1656 (icalendar--dmsg "all day event")
1657 (setq diary-string
(icalendar--datetime-to-diary-date
1660 ;; add all other elements unless the user doesn't want to have
1665 (concat diary-string
" "
1666 (icalendar--format-ical-event e
)))
1667 (if do-not-ask
(setq summary nil
))
1668 (icalendar--add-diary-entry diary-string diary-file
1669 non-marking summary
))
1671 (setq found-error t
)
1673 (format "%s\nCannot handle this event:%s"
1675 ;; FIXME: inform user about ignored event properties
1678 (message "Ignoring event \"%s\"" e
)
1679 (setq found-error t
)
1680 (setq error-string
(format "%s\n%s\nCannot handle this event: %s"
1681 error-val error-string e
))
1682 (message "%s" error-string
))))
1684 (save-current-buffer
1685 (set-buffer (get-buffer-create "*icalendar-errors*"))
1687 (insert error-string
)))
1688 (message "Converting icalendar...done")
1691 ;; subroutines for importing
1692 (defun icalendar--convert-recurring-to-diary (e dtstart-dec start-t end-t
)
1693 "Convert recurring icalendar event E to diary format.
1695 DTSTART-DEC is the DTSTART property of E.
1696 START-T is the event's start time in diary format.
1697 END-T is the event's end time in diary format."
1698 (icalendar--dmsg "recurring event")
1699 (let* ((rrule (icalendar--get-event-property e
'RRULE
))
1700 (rrule-props (icalendar--split-value rrule
))
1701 (frequency (cadr (assoc 'FREQ rrule-props
)))
1702 (until (cadr (assoc 'UNTIL rrule-props
)))
1703 (count (cadr (assoc 'COUNT rrule-props
)))
1704 (interval (read (or (cadr (assoc 'INTERVAL rrule-props
)) "1")))
1705 (dtstart-conv (icalendar--datetime-to-diary-date dtstart-dec
))
1706 (until-conv (icalendar--datetime-to-diary-date
1707 (icalendar--decode-isodatetime until
)))
1708 (until-1-conv (icalendar--datetime-to-diary-date
1709 (icalendar--decode-isodatetime until -
1)))
1712 ;; FIXME FIXME interval!!!!!!!!!!!!!
1716 (message "Must not have UNTIL and COUNT -- ignoring COUNT element!")
1718 (cond ((string-equal frequency
"DAILY")
1719 (setq until
(icalendar--add-decoded-times
1721 (list 0 0 0 (* (read count
) interval
) 0 0)))
1722 (setq until-1
(icalendar--add-decoded-times
1724 (list 0 0 0 (* (- (read count
) 1) interval
)
1727 ((string-equal frequency
"WEEKLY")
1728 (setq until
(icalendar--add-decoded-times
1730 (list 0 0 0 (* (read count
) 7 interval
) 0 0)))
1731 (setq until-1
(icalendar--add-decoded-times
1733 (list 0 0 0 (* (- (read count
) 1) 7
1736 ((string-equal frequency
"MONTHLY")
1737 (setq until
(icalendar--add-decoded-times
1738 dtstart-dec
(list 0 0 0 0 (* (- (read count
) 1)
1740 (setq until-1
(icalendar--add-decoded-times
1741 dtstart-dec
(list 0 0 0 0 (* (- (read count
) 1)
1744 ((string-equal frequency
"YEARLY")
1745 (setq until
(icalendar--add-decoded-times
1746 dtstart-dec
(list 0 0 0 0 0 (* (- (read count
) 1)
1748 (setq until-1
(icalendar--add-decoded-times
1750 (list 0 0 0 0 0 (* (- (read count
) 1)
1754 (message "Cannot handle COUNT attribute for `%s' events."
1756 (setq until-conv
(icalendar--datetime-to-diary-date until
))
1757 (setq until-1-conv
(icalendar--datetime-to-diary-date until-1
))
1760 (cond ((string-equal frequency
"WEEKLY")
1763 ;; weekly and all-day
1764 (icalendar--dmsg "weekly all-day")
1769 "(diary-cyclic %d %s) "
1770 "(diary-block %s %s))")
1774 (if count until-1-conv until-conv
)
1777 (format "%%%%(and (diary-cyclic %d %s))"
1780 ;; weekly and not all-day
1781 (let* ((byday (cadr (assoc 'BYDAY rrule-props
)))
1783 (icalendar--get-weekday-number byday
)))
1784 (icalendar--dmsg "weekly not-all-day")
1789 "(diary-cyclic %d %s) "
1790 "(diary-block %s %s)) "
1797 (if end-t
"-" "") (or end-t
"")))
1800 ;; DTSTART;VALUE=DATE-TIME:20030919T090000
1801 ;; DTEND;VALUE=DATE-TIME:20030919T113000
1804 "%%%%(and (diary-cyclic %s %s)) %s%s%s"
1808 (if end-t
"-" "") (or end-t
"")))))))
1810 ((string-equal frequency
"YEARLY")
1811 (icalendar--dmsg "yearly")
1813 (setq result
(format
1814 (concat "%%%%(and (diary-date %s %s t) "
1815 "(diary-block %s %s)) %s%s%s")
1816 (if european-calendar-style
(nth 3 dtstart-dec
)
1817 (nth 4 dtstart-dec
))
1818 (if european-calendar-style
(nth 4 dtstart-dec
)
1819 (nth 3 dtstart-dec
))
1823 (if end-t
"-" "") (or end-t
"")))
1824 (setq result
(format
1825 "%%%%(and (diary-anniversary %s)) %s%s%s"
1828 (if end-t
"-" "") (or end-t
"")))))
1830 ((string-equal frequency
"MONTHLY")
1831 (icalendar--dmsg "monthly")
1834 "%%%%(and (diary-date %s %s %s) (diary-block %s %s)) %s%s%s"
1835 (if european-calendar-style
(nth 3 dtstart-dec
) "t")
1836 (if european-calendar-style
"t" (nth 3 dtstart-dec
))
1841 "1 1 9999") ;; FIXME: should be unlimited
1843 (if end-t
"-" "") (or end-t
""))))
1845 ((and (string-equal frequency
"DAILY"))
1849 (concat "%%%%(and (diary-cyclic %s %s) "
1850 "(diary-block %s %s)) %s%s%s")
1851 interval dtstart-conv dtstart-conv
1852 (if count until-1-conv until-conv
)
1854 (if end-t
"-" "") (or end-t
"")))
1857 "%%%%(and (diary-cyclic %s %s)) %s%s%s"
1861 (if end-t
"-" "") (or end-t
""))))))
1862 ;; Handle exceptions from recurrence rules
1863 (let ((ex-dates (icalendar--get-event-properties e
'EXDATE
)))
1865 (let* ((ex-start (icalendar--decode-isodatetime
1867 (ex-d (icalendar--datetime-to-diary-date
1870 (icalendar--rris "^%%(\\(and \\)?"
1872 "%%%%(and (not (diary-date %s)) "
1875 (setq ex-dates
(cdr ex-dates
))))
1876 ;; FIXME: exception rules are not recognized
1877 (if (icalendar--get-event-property e
'EXRULE
)
1880 "\n Exception rules: "
1881 (icalendar--get-event-properties
1885 (defun icalendar--convert-non-recurring-all-day-to-diary (event start-d end-d
)
1886 "Convert non-recurring icalendar EVENT to diary format.
1888 DTSTART is the decoded DTSTART property of E.
1889 Argument START-D gives the first day.
1890 Argument END-D gives the last day."
1891 (icalendar--dmsg "non-recurring all-day event")
1892 (format "%%%%(and (diary-block %s %s))" start-d end-d
))
1894 (defun icalendar--convert-non-recurring-not-all-day-to-diary (event dtstart-dec
1898 "Convert recurring icalendar EVENT to diary format.
1900 DTSTART-DEC is the decoded DTSTART property of E.
1901 DTEND-DEC is the decoded DTEND property of E.
1902 START-T is the event's start time in diary format.
1903 END-T is the event's end time in diary format."
1904 (icalendar--dmsg "not all day event")
1907 (icalendar--datetime-to-diary-date
1912 (icalendar--datetime-to-diary-date
1916 (defun icalendar--add-diary-entry (string diary-file non-marking
1918 "Add STRING to the diary file DIARY-FILE.
1919 STRING must be a properly formatted valid diary entry. NON-MARKING
1920 determines whether diary events are created as non-marking. If
1921 SUMMARY is not nil it must be a string that gives the summary of the
1922 entry. In this case the user will be asked whether he wants to insert
1924 (when (or (not summary
)
1925 (y-or-n-p (format "Add appointment for `%s' to diary? "
1929 (y-or-n-p (format "Make appointment non-marking? "))))
1930 (save-window-excursion
1933 (read-file-name "Add appointment to this diary file: ")))
1934 ;; Note: make-diary-entry will add a trailing blank char.... :(
1935 (make-diary-entry string non-marking diary-file
))))
1937 (provide 'icalendar
)
1939 ;; arch-tag: 74fdbe8e-0451-4e38-bb61-4416e822f4fc
1940 ;;; icalendar.el ends here