(set-auto-mode-0): Don't rely on dynamic binding of
[bpt/emacs.git] / lisp / calendar / icalendar.el
CommitLineData
e0cd68ee 1;;; icalendar.el --- iCalendar implementation -*-coding: utf-8 -*-
707c20a8
GM
2
3;; Copyright (C) 2002, 2003, 2004 Free Software Foundation, Inc.
4
e0cd68ee
GM
5;; Author: Ulf Jasper <ulf.jasper@web.de>
6;; Created: August 2002
7;; Keywords: calendar
707c20a8
GM
8;; Human-Keywords: calendar, diary, iCalendar, vCalendar
9
10;; This file is part of GNU Emacs.
11
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)
15;; any later version.
16
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.
21
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., 59 Temple Place - Suite 330,
25;; Boston, MA 02111-1307, USA.
26
27;;; Commentary:
28
29;; This package is documented in the Emacs Manual.
30
31
32;;; History:
33
e0cd68ee
GM
34;; 0.07: Renamed commands!
35;; icalendar-extract-ical-from-buffer -> icalendar-import-buffer
36;; icalendar-convert-diary-to-ical -> icalendar-export-file
37;; Naming scheme: icalendar-.* = user command; icalendar--.* =
38;; internal.
39;; Added icalendar-export-region.
40;; The import and export commands do not clear their target file,
41;; but append their results to the target file.
f2aa5449
GM
42;; I18n-problems fixed -- use calendar-(month|day)-name-array.
43;; Fixed problems with export of multi-line diary entries.
e0cd68ee
GM
44
45;; 0.06: Bugfixes regarding icalendar-import-format-*.
46;; Fix in icalendar-convert-diary-to-ical -- thanks to Philipp
47;; Grau.
707c20a8
GM
48
49;; 0.05: New import format scheme: Replaced icalendar-import-prefix-*,
50;; icalendar-import-ignored-properties, and
51;; icalendar-import-separator with icalendar-import-format(-*).
52;; icalendar-import-file and icalendar-convert-diary-to-ical
53;; have an extra parameter which should prevent them from
54;; erasing their target files (untested!).
55;; Tested with Emacs 21.3.2
56
57;; 0.04: Bugfix: import: double quoted param values did not work
58;; Read DURATION property when importing.
59;; Added parameter icalendar-duration-correction.
60
61;; 0.03: Export takes care of european-calendar-style.
62;; Tested with Emacs 21.3.2 and XEmacs 21.4.12
63
64;; 0.02: Should work in XEmacs now. Thanks to Len Trigg for the
65;; XEmacs patches!
66;; Added exporting from Emacs diary to ical.
67;; Some bugfixes, after testing with calendars from
68;; http://icalshare.com.
69;; Tested with Emacs 21.3.2 and XEmacs 21.4.12
70
71;; 0.01: First published version. Trial version. Alpha version.
72
73;; ======================================================================
74;; To Do:
75
e0cd68ee 76;; * Import from ical to diary:
707c20a8
GM
77;; + Need more properties for icalendar-import-format
78;; + check vcalendar version
79;; + check (unknown) elements
80;; + recurring events!
81;; + works for european style calendars only! Does it?
82;; + alarm
83;; + exceptions in recurring events
84;; + the parser is too soft
85;; + error log is incomplete
86;; + nice to have: #include "webcal://foo.com/some-calendar.ics"
87
e0cd68ee 88;; * Export from diary to ical
707c20a8
GM
89;; + diary-date, diary-float, and self-made sexp entries are not
90;; understood
91;; + timezones, currently all times are local!
92
93;; * Other things
707c20a8
GM
94;; + clean up all those date/time parsing functions
95;; + Handle todo items?
96;; + Check iso 8601 for datetime and period
97;; + Which chars to (un)escape?
707c20a8
GM
98
99
100;;; Code:
101
e0cd68ee 102(defconst icalendar-version 0.07
707c20a8
GM
103 "Version number of icalendar.el.")
104
105;; ======================================================================
106;; Customizables
107;; ======================================================================
108(defgroup icalendar nil
109 "Icalendar support."
110 :prefix "icalendar-"
111 :group 'calendar)
112
113(defcustom icalendar-import-format
114 "%s%d%l%o"
115 "Format string for importing events from iCalendar into Emacs diary.
116This string defines how iCalendar events are inserted into diary
117file. Meaning of the specifiers:
118%d Description, see `icalendar-import-format-description'
119%l Location, see `icalendar-import-format-location'
120%o Organizer, see `icalendar-import-format-organizer'
121%s Subject, see `icalendar-import-format-subject'"
122 :type 'string
123 :group 'icalendar)
124
125(defcustom icalendar-import-format-subject
126 "%s"
127 "Format string defining how the subject element is formatted.
128This applies only if the subject is not empty! `%s' is replaced
129by the subject."
130 :type 'string
131 :group 'icalendar)
132
133(defcustom icalendar-import-format-description
134 "\n Desc: %s"
135 "Format string defining how the description element is formatted.
136This applies only if the description is not empty! `%s' is
137replaced by the description."
138 :type 'string
139 :group 'icalendar)
140
141(defcustom icalendar-import-format-location
142 "\n Location: %s"
143 "Format string defining how the location element is formatted.
144This applies only if the location is not empty! `%s' is replaced
145by the location."
146 :type 'string
147 :group 'icalendar)
148
149(defcustom icalendar-import-format-organizer
150 "\n Organizer: %s"
151 "Format string defining how the organizer element is formatted.
152This applies only if the organizer is not empty! `%s' is
153replaced by the organizer."
154 :type 'string
155 :group 'icalendar)
156
157(defcustom icalendar-duration-correction
158 t
159 "Workaround for all-day events.
160If non-nil the length=duration of iCalendar appointments that
161have a length of exactly n days is decreased by one day. This
162fixes problems with all-day events, which appear to be one day
163longer than they are."
164 :type 'boolean
165 :group 'icalendar)
166
167
168;; ======================================================================
169;; NO USER SERVICABLE PARTS BELOW THIS LINE
170;; ======================================================================
171
f2aa5449 172(defconst icalendar--weekday-array ["SU" "MO" "TU" "WE" "TH" "FR" "SA"])
707c20a8
GM
173
174(defvar icalendar-debug nil ".")
175
176;; ======================================================================
177;; all the other libs we need
178;; ======================================================================
179(require 'calendar)
180(require 'appt)
181
e0cd68ee
GM
182;; ======================================================================
183;; misc
184;; ======================================================================
185(defun icalendar--dmsg (&rest args)
186 "Print message ARGS if `icalendar-debug' is non-nil."
187 (if icalendar-debug
188 (apply 'message args)))
189
707c20a8
GM
190;; ======================================================================
191;; Core functionality
192;; Functions for parsing icalendars, importing and so on
193;; ======================================================================
194
e0cd68ee 195(defun icalendar--get-unfolded-buffer (folded-ical-buffer)
707c20a8
GM
196 "Return a new buffer containing the unfolded contents of a buffer.
197Folding is the iCalendar way of wrapping long lines. In the
198created buffer all occurrences of CR LF BLANK are replaced by the
199empty string. Argument FOLDED-ICAL-BUFFER is the unfolded input
200buffer."
201 (let ((unfolded-buffer (get-buffer-create " *icalendar-work*")))
202 (save-current-buffer
203 (set-buffer unfolded-buffer)
204 (erase-buffer)
205 (insert-buffer folded-ical-buffer)
206 (while (re-search-forward "\r?\n[ \t]" nil t)
e0cd68ee 207 (replace-match "" nil nil)))
707c20a8
GM
208 unfolded-buffer))
209
e0cd68ee
GM
210(defsubst icalendar--rris (re rp st)
211 "Replace regexp RE with RP in string ST and return the new string.
212This is here for compatibility with XEmacs."
707c20a8
GM
213 ;; XEmacs:
214 (if (fboundp 'replace-in-string)
215 (save-match-data ;; apparently XEmacs needs save-match-data
216 (replace-in-string st re rp))
217 ;; Emacs:
218 (replace-regexp-in-string re rp st)))
219
e0cd68ee 220(defun icalendar--read-element (invalue inparams)
707c20a8
GM
221 "Recursively read the next iCalendar element in the current buffer.
222INVALUE gives the current iCalendar element we are reading.
223INPARAMS gives the current parameters.....
224This function calls itself recursively for each nested calendar element
225it finds"
226 (let (element children line name params param param-name param-value
227 value
e0cd68ee 228 (continue t))
707c20a8
GM
229 (setq children '())
230 (while (and continue
231 (re-search-forward "^\\([A-Za-z0-9-]+\\)[;:]" nil t))
232 (setq name (intern (match-string 1)))
233 (backward-char 1)
234 (setq params '())
235 (setq line '())
236 (while (looking-at ";")
237 (re-search-forward ";\\([A-Za-z0-9-]+\\)=" nil nil)
238 (setq param-name (intern (match-string 1)))
239 (re-search-forward "\\(\\([^;,:\"]+\\)\\|\"\\([^\"]+\\)\"\\)[;:]"
240 nil t)
241 (backward-char 1)
242 (setq param-value (or (match-string 2) (match-string 3)))
243 (setq param (list param-name param-value))
244 (while (looking-at ",")
245 (re-search-forward "\\(\\([^;,:]+\\)\\|\"\\([^\"]+\\)\"\\)"
246 nil t)
247 (if (match-string 2)
248 (setq param-value (match-string 2))
249 (setq param-value (match-string 3)))
250 (setq param (append param param-value)))
251 (setq params (append params param)))
252 (unless (looking-at ":")
253 (error "Oops"))
254 (forward-char 1)
255 (re-search-forward "\\(.*\\)\\(\r?\n[ \t].*\\)*" nil t)
e0cd68ee 256 (setq value (icalendar--rris "\r?\n[ \t]" "" (match-string 0)))
707c20a8
GM
257 (setq line (list name params value))
258 (cond ((eq name 'BEGIN)
259 (setq children
260 (append children
e0cd68ee
GM
261 (list (icalendar--read-element (intern value)
262 params)))))
707c20a8
GM
263 ((eq name 'END)
264 (setq continue nil))
265 (t
266 (setq element (append element (list line))))))
267 (if invalue
268 (list invalue inparams element children)
269 children)))
270
271;; ======================================================================
272;; helper functions for examining events
273;; ======================================================================
274
e0cd68ee
GM
275;;(defsubst icalendar--get-all-event-properties (event)
276;; "Return the list of properties in this EVENT."
277;; (car (cddr event)))
707c20a8 278
e0cd68ee 279(defun icalendar--get-event-property (event prop)
707c20a8
GM
280 "For the given EVENT return the value of the property PROP."
281 (catch 'found
282 (let ((props (car (cddr event))) pp)
283 (while props
284 (setq pp (car props))
285 (if (eq (car pp) prop)
286 (throw 'found (car (cddr pp))))
287 (setq props (cdr props))))
288 nil))
289
e0cd68ee
GM
290;; (defun icalendar--set-event-property (event prop new-value)
291;; "For the given EVENT set the property PROP to the value NEW-VALUE."
292;; (catch 'found
293;; (let ((props (car (cddr event))) pp)
294;; (while props
295;; (setq pp (car props))
296;; (when (eq (car pp) prop)
297;; (setcdr (cdr pp) new-value)
298;; (throw 'found (car (cddr pp))))
299;; (setq props (cdr props)))
300;; (setq props (car (cddr event)))
301;; (setcar (cddr event)
302;; (append props (list (list prop nil new-value)))))))
303
304(defun icalendar--get-children (node name)
707c20a8
GM
305 "Return all children of the given NODE which have a name NAME.
306For instance the VCALENDAR node can have VEVENT children as well as VTODO
307children."
308 (let ((result nil)
309 (children (cadr (cddr node))))
310 (when (eq (car node) name)
311 (setq result node))
312 ;;(message "%s" node)
313 (when children
314 (let ((subresult
315 (delq nil
e0cd68ee
GM
316 (mapcar (lambda (n)
317 (icalendar--get-children n name))
318 children))))
707c20a8
GM
319 (if subresult
320 (if result
321 (setq result (append result subresult))
322 (setq result subresult)))))
323 result))
324
e0cd68ee
GM
325 ; private
326(defun icalendar--all-events (icalendar)
707c20a8 327 "Return the list of all existing events in the given ICALENDAR."
e0cd68ee 328 (icalendar--get-children (car icalendar) 'VEVENT))
707c20a8 329
e0cd68ee 330(defun icalendar--split-value (value-string)
707c20a8
GM
331 "Splits VALUE-STRING at ';='."
332 (let ((result '())
333 param-name param-value)
334 (when value-string
335 (save-current-buffer
336 (set-buffer (get-buffer-create " *ical-temp*"))
337 (set-buffer-modified-p nil)
338 (erase-buffer)
339 (insert value-string)
340 (goto-char (point-min))
341 (while
e0cd68ee
GM
342 (re-search-forward
343 "\\([A-Za-z0-9-]+\\)=\\(\\([^;,:]+\\)\\|\"\\([^\"]+\\)\"\\);?"
344 nil t)
707c20a8
GM
345 (setq param-name (intern (match-string 1)))
346 (setq param-value (match-string 2))
347 (setq result
e0cd68ee 348 (append result (list (list param-name param-value)))))))
707c20a8
GM
349 result))
350
e0cd68ee 351(defun icalendar--decode-isodatetime (isodatetimestring)
707c20a8
GM
352 "Return ISODATETIMESTRING in format like `decode-time'.
353Converts from ISO-8601 to Emacs representation. If ISODATETIMESTRING
354specifies UTC time (trailing letter Z) the decoded time is given in
355the local time zone! FIXME: TZID-attributes are ignored....! FIXME:
356multiple comma-separated values should be allowed!"
e0cd68ee 357 (icalendar--dmsg isodatetimestring)
707c20a8
GM
358 (if isodatetimestring
359 ;; day/month/year must be present
360 (let ((year (read (substring isodatetimestring 0 4)))
361 (month (read (substring isodatetimestring 4 6)))
362 (day (read (substring isodatetimestring 6 8)))
363 (hour 0)
364 (minute 0)
365 (second 0))
366 (when (> (length isodatetimestring) 12)
e0cd68ee 367 ;; hour/minute present
707c20a8
GM
368 (setq hour (read (substring isodatetimestring 9 11)))
369 (setq minute (read (substring isodatetimestring 11 13))))
370 (when (> (length isodatetimestring) 14)
e0cd68ee 371 ;; seconds present
707c20a8
GM
372 (setq second (read (substring isodatetimestring 13 15))))
373 (when (and (> (length isodatetimestring) 15)
e0cd68ee 374 ;; UTC specifier present
707c20a8
GM
375 (char-equal ?Z (aref isodatetimestring 15)))
376 ;; if not UTC add current-time-zone offset
377 (setq second (+ (car (current-time-zone)) second)))
378 ;; create the decoded date-time
379 ;; FIXME!?!
380 (condition-case nil
381 (decode-time (encode-time second minute hour day month year))
382 (error
383 (message "Cannot decode \"%s\"" isodatetimestring)
384 ;; hope for the best...
385 (list second minute hour day month year 0 nil 0))))
386 ;; isodatetimestring == nil
387 nil))
388
e0cd68ee 389(defun icalendar--decode-isoduration (isodurationstring)
707c20a8
GM
390 "Return ISODURATIONSTRING in format like `decode-time'.
391Converts from ISO-8601 to Emacs representation. If ISODURATIONSTRING
392specifies UTC time (trailing letter Z) the decoded time is given in
393the local time zone! FIXME: TZID-attributes are ignored....! FIXME:
394multiple comma-separated values should be allowed!"
395 (if isodurationstring
396 (save-match-data
397 (string-match
398 (concat
399 "^P[+-]?\\("
400 "\\(\\([0-9]+\\)D\\)" ; days only
401 "\\|"
402 "\\(\\(\\([0-9]+\\)D\\)?T\\(\\([0-9]+\\)H\\)?" ; opt days
e0cd68ee 403 "\\(\\([0-9]+\\)M\\)?\\(\\([0-9]+\\)S\\)?\\)" ; mand. time
707c20a8
GM
404 "\\|"
405 "\\(\\([0-9]+\\)W\\)" ; weeks only
406 "\\)$") isodurationstring)
407 (let ((seconds 0)
408 (minutes 0)
409 (hours 0)
410 (days 0)
411 (months 0)
412 (years 0))
e0cd68ee
GM
413 (cond
414 ((match-beginning 2) ;days only
415 (setq days (read (substring isodurationstring
416 (match-beginning 3)
417 (match-end 3))))
418 (when icalendar-duration-correction
419 (setq days (1- days))))
420 ((match-beginning 4) ;days and time
421 (if (match-beginning 5)
422 (setq days (* 7 (read (substring isodurationstring
423 (match-beginning 6)
424 (match-end 6))))))
425 (if (match-beginning 7)
426 (setq hours (read (substring isodurationstring
427 (match-beginning 8)
428 (match-end 8)))))
429 (if (match-beginning 9)
430 (setq minutes (read (substring isodurationstring
431 (match-beginning 10)
432 (match-end 10)))))
433 (if (match-beginning 11)
434 (setq seconds (read (substring isodurationstring
435 (match-beginning 12)
436 (match-end 12)))))
437 )
438 ((match-beginning 13) ;weeks only
439 (setq days (* 7 (read (substring isodurationstring
440 (match-beginning 14)
441 (match-end 14))))))
442 )
443 (list seconds minutes hours days months years)))
707c20a8
GM
444 ;; isodatetimestring == nil
445 nil))
446
e0cd68ee 447(defun icalendar--add-decoded-times (time1 time2)
707c20a8
GM
448 "Add TIME1 to TIME2.
449Both times must be given in decoded form. One of these times must be
450valid (year > 1900 or something)."
451 ;; FIXME: does this function exist already?
452 (decode-time (encode-time
453 (+ (nth 0 time1) (nth 0 time2))
454 (+ (nth 1 time1) (nth 1 time2))
455 (+ (nth 2 time1) (nth 2 time2))
456 (+ (nth 3 time1) (nth 3 time2))
457 (+ (nth 4 time1) (nth 4 time2))
458 (+ (nth 5 time1) (nth 5 time2))
459 nil
460 nil
461 ;;(or (nth 6 time1) (nth 6 time2)) ;; FIXME?
462 )))
463
e0cd68ee 464(defun icalendar--datetime-to-noneuropean-date (datetime)
707c20a8
GM
465 "Convert the decoded DATETIME to non-european-style format.
466Non-European format: (month day year)."
467 (if datetime
e0cd68ee
GM
468 (list (nth 4 datetime) ;month
469 (nth 3 datetime) ;day
470 (nth 5 datetime)) ;year
707c20a8
GM
471 ;; datetime == nil
472 nil))
473
e0cd68ee 474(defun icalendar--datetime-to-european-date (datetime)
707c20a8
GM
475 "Convert the decoded DATETIME to European format.
476European format: (day month year).
477FIXME"
478 (if datetime
e0cd68ee
GM
479 (format "%d %d %d" (nth 3 datetime) ; day
480 (nth 4 datetime) ;month
481 (nth 5 datetime)) ;year
707c20a8
GM
482 ;; datetime == nil
483 nil))
484
e0cd68ee 485(defun icalendar--datetime-to-colontime (datetime)
707c20a8
GM
486 "Extract the time part of a decoded DATETIME into 24-hour format.
487Note that this silently ignores seconds."
488 (format "%02d:%02d" (nth 2 datetime) (nth 1 datetime)))
489
e0cd68ee 490(defun icalendar--get-month-number (monthname)
707c20a8 491 "Return the month number for the given MONTHNAME."
f2aa5449
GM
492 (catch 'found
493 (let ((num 1)
494 (m (downcase monthname)))
495 (mapc (lambda (month)
496 (let ((mm (downcase month)))
497 (if (or (string-equal mm m)
498 (string-equal (substring mm 0 3) m))
499 (throw 'found num))
500 (setq num (1+ num))))
501 calendar-month-name-array))
502 ;; Error:
503 -1))
504
505(defun icalendar--get-weekday-number (abbrevweekday)
506 "Return the number for the ABBREVWEEKDAY."
507 (catch 'found
508 (let ((num 0)
509 (aw (downcase abbrevweekday)))
510 (mapc (lambda (day)
511 (let ((d (downcase day)))
512 (if (string-equal d aw)
513 (throw 'found num))
514 (setq num (1+ num))))
515 icalendar--weekday-array))
516 ;; Error:
517 -1))
707c20a8 518
e0cd68ee 519(defun icalendar--get-weekday-abbrev (weekday)
707c20a8 520 "Return the abbreviated WEEKDAY."
f2aa5449
GM
521 (catch 'found
522 (let ((num 0)
523 (w (downcase weekday)))
524 (mapc (lambda (day)
525 (let ((d (downcase day)))
526 (if (or (string-equal d w)
527 (string-equal (substring d 0 3) w))
528 (throw 'found (aref icalendar--weekday-array num)))
529 (setq num (1+ num))))
530 calendar-day-name-array))
531 ;; Error:
532 "??"))
707c20a8 533
e0cd68ee 534(defun icalendar--datestring-to-isodate (datestring &optional day-shift)
707c20a8
GM
535 "Convert diary-style DATESTRING to iso-style date.
536If DAY-SHIFT is non-nil, the result is shifted by DAY-SHIFT days
537-- DAY-SHIFT must be either nil or an integer. This function
538takes care of european-style."
539 (let ((day -1) month year)
540 (save-match-data
e0cd68ee
GM
541 (cond ( ;; numeric date
542 (string-match (concat "\\s-*"
543 "0?\\([1-9][0-9]?\\)[ \t/]\\s-*"
544 "0?\\([1-9][0-9]?\\),?[ \t/]\\s-*"
545 "\\([0-9]\\{4\\}\\)")
546 datestring)
547 (setq day (read (substring datestring (match-beginning 1)
548 (match-end 1))))
549 (setq month (read (substring datestring (match-beginning 2)
550 (match-end 2))))
551 (setq year (read (substring datestring (match-beginning 3)
552 (match-end 3))))
553 (unless european-calendar-style
554 (let ((x month))
555 (setq month day)
556 (setq day x))))
557 ( ;; date contains month names -- european-style
558 (and european-calendar-style
559 (string-match (concat "\\s-*"
560 "0?\\([123]?[0-9]\\)[ \t/]\\s-*"
561 "\\([A-Za-z][^ ]+\\)[ \t/]\\s-*"
562 "\\([0-9]\\{4\\}\\)")
563 datestring))
564 (setq day (read (substring datestring (match-beginning 1)
565 (match-end 1))))
566 (setq month (icalendar--get-month-number
567 (substring datestring (match-beginning 2)
568 (match-end 2))))
569 (setq year (read (substring datestring (match-beginning 3)
570 (match-end 3)))))
571 ( ;; date contains month names -- non-european-style
572 (and (not european-calendar-style)
573 (string-match (concat "\\s-*"
574 "\\([A-Za-z][^ ]+\\)[ \t/]\\s-*"
575 "0?\\([123]?[0-9]\\),?[ \t/]\\s-*"
576 "\\([0-9]\\{4\\}\\)")
577 datestring))
578 (setq day (read (substring datestring (match-beginning 2)
579 (match-end 2))))
580 (setq month (icalendar--get-month-number
581 (substring datestring (match-beginning 1)
582 (match-end 1))))
583 (setq year (read (substring datestring (match-beginning 3)
584 (match-end 3)))))
585 (t
586 nil)))
707c20a8 587 (if (> day 0)
e0cd68ee
GM
588 (let ((mdy (calendar-gregorian-from-absolute
589 (+ (calendar-absolute-from-gregorian (list month day
590 year))
591 (or day-shift 0)))))
592 (format "%04d%02d%02d" (nth 2 mdy) (nth 0 mdy) (nth 1 mdy)))
707c20a8
GM
593 nil)))
594
e0cd68ee 595(defun icalendar--diarytime-to-isotime (timestring ampmstring)
707c20a8
GM
596 "Convert a a time like 9:30pm to an iso-conform string like T213000.
597In this example the TIMESTRING would be \"9:30\" and the AMPMSTRING
598would be \"pm\"."
599 (if timestring
e0cd68ee 600 (let ((starttimenum (read (icalendar--rris ":" "" timestring))))
707c20a8
GM
601 ;; take care of am/pm style
602 (if (and ampmstring (string= "pm" ampmstring))
603 (setq starttimenum (+ starttimenum 1200)))
604 (format "T%04d00" starttimenum))
605 nil))
606
e0cd68ee 607(defun icalendar--convert-string-for-export (s)
707c20a8 608 "Escape comma and other critical characters in string S."
e0cd68ee 609 (icalendar--rris "," "\\\\," s))
707c20a8 610
e0cd68ee 611(defun icalendar--convert-string-for-import (string)
707c20a8 612 "Remove escape chars for comma, semicolon etc. from STRING."
e0cd68ee
GM
613 (icalendar--rris
614 "\\\\n" "\n " (icalendar--rris
615 "\\\\\"" "\"" (icalendar--rris
616 "\\\\;" ";" (icalendar--rris
617 "\\\\," "," string)))))
707c20a8
GM
618
619;; ======================================================================
e0cd68ee 620;; Export -- convert emacs-diary to icalendar
707c20a8
GM
621;; ======================================================================
622
e0cd68ee
GM
623;; User function
624(defun icalendar-export-file (diary-filename ical-filename)
625 "Export diary file to iCalendar format.
626All diary entries in the file DIARY-FILENAME are converted to iCalendar
627format. The result is appended to the file ICAL-FILENAME."
707c20a8
GM
628 (interactive "FExport diary data from file:
629Finto iCalendar file: ")
e0cd68ee
GM
630 (save-current-buffer
631 (set-buffer (find-file diary-filename))
632 (icalendar-export-region (point-min) (point-max) ical-filename)))
633
634(defalias 'icalendar-convert-diary-to-ical 'icalendar-export-file)
635(make-obsolete 'icalendar-convert-diary-to-ical 'icalendar-export-file
636 "icalendar 0.07")
637
638;; User function
639(defun icalendar-export-region (min max ical-filename)
640 "Export region in diary file to iCalendar format.
641All diary entries in the region from MIN to MAX in the current buffer are
642converted to iCalendar format. The result is appended to the file
643ICAL-FILENAME."
644 (interactive "r
645FExport diary data into iCalendar file: ")
707c20a8
GM
646 (let ((result "")
647 (start 0)
648 (entry-main "")
649 (entry-rest "")
650 (header "")
651 (contents)
652 (oops nil)
653 (nonmarker (concat "^" (regexp-quote diary-nonmarking-symbol)
e0cd68ee
GM
654 "?")))
655 (save-excursion
656 (goto-char min)
707c20a8 657 (while (re-search-forward
f2aa5449 658 "^\\([^ \t\n].*\\)\\(\\(\n[ \t].*\\)*\\)" max t)
707c20a8
GM
659 (setq entry-main (match-string 1))
660 (if (match-beginning 2)
661 (setq entry-rest (match-string 2))
662 (setq entry-rest ""))
663 (setq header (format "\nBEGIN:VEVENT\nUID:emacs%d%d%d"
664 (car (current-time))
665 (cadr (current-time))
666 (car (cddr (current-time)))))
667 (setq oops nil)
668 (cond
669 ;; anniversaries
670 ((string-match
671 (concat nonmarker
672 "%%(diary-anniversary \\([^)]+\\))\\s-*\\(.*\\)")
673 entry-main)
e0cd68ee 674 (icalendar--dmsg "diary-anniversary %s" entry-main)
707c20a8
GM
675 (let* ((datetime (substring entry-main (match-beginning 1)
676 (match-end 1)))
e0cd68ee 677 (summary (icalendar--convert-string-for-export
707c20a8
GM
678 (substring entry-main (match-beginning 2)
679 (match-end 2))))
e0cd68ee
GM
680 (startisostring (icalendar--datestring-to-isodate
681 datetime))
682 (endisostring (icalendar--datestring-to-isodate
683 datetime 1)))
707c20a8
GM
684 (setq contents
685 (concat "\nDTSTART;VALUE=DATE:" startisostring
686 "\nDTEND;VALUE=DATE:" endisostring
687 "\nSUMMARY:" summary
688 "\nRRULE:FREQ=YEARLY;INTERVAL=1"
689 ;; the following is redundant,
690 ;; but korganizer seems to expect this... ;(
691 ;; and evolution doesn't understand it... :(
692 ;; so... who is wrong?!
693 ";BYMONTH=" (substring startisostring 4 6)
694 ";BYMONTHDAY=" (substring startisostring 6 8)
695 )))
696 (unless (string= entry-rest "")
697 (setq contents (concat contents "\nDESCRIPTION:"
e0cd68ee 698 (icalendar--convert-string-for-export
707c20a8
GM
699 entry-rest)))))
700 ;; cyclic events
701 ;; %%(diary-cyclic )
702 ((string-match
703 (concat nonmarker
704 "%%(diary-cyclic \\([^ ]+\\) +"
705 "\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\))\\s-*\\(.*\\)")
706 entry-main)
e0cd68ee 707 (icalendar--dmsg "diary-cyclic %s" entry-main)
707c20a8
GM
708 (let* ((frequency (substring entry-main (match-beginning 1)
709 (match-end 1)))
710 (datetime (substring entry-main (match-beginning 2)
711 (match-end 2)))
e0cd68ee 712 (summary (icalendar--convert-string-for-export
707c20a8
GM
713 (substring entry-main (match-beginning 3)
714 (match-end 3))))
e0cd68ee
GM
715 (startisostring (icalendar--datestring-to-isodate
716 datetime))
717 (endisostring (icalendar--datestring-to-isodate
718 datetime 1)))
707c20a8
GM
719 (setq contents
720 (concat "\nDTSTART;VALUE=DATE:" startisostring
721 "\nDTEND;VALUE=DATE:" endisostring
722 "\nSUMMARY:" summary
723 "\nRRULE:FREQ=DAILY;INTERVAL=" frequency
724 ;; strange: korganizer does not expect
725 ;; BYSOMETHING here...
726 )))
727 (unless (string= entry-rest "")
728 (setq contents (concat contents "\nDESCRIPTION:"
e0cd68ee 729 (icalendar--convert-string-for-export
707c20a8
GM
730 entry-rest)))))
731 ;; diary-date -- FIXME
732 ((string-match
733 (concat nonmarker
734 "%%(diary-date \\([^)]+\\))\\s-*\\(.*\\)")
735 entry-main)
e0cd68ee 736 (icalendar--dmsg "diary-date %s" entry-main)
707c20a8
GM
737 (setq oops t))
738 ;; float events -- FIXME
739 ((string-match
740 (concat nonmarker
741 "%%(diary-float \\([^)]+\\))\\s-*\\(.*\\)")
742 entry-main)
e0cd68ee 743 (icalendar--dmsg "diary-float %s" entry-main)
707c20a8
GM
744 (setq oops t))
745 ;; block events
746 ((string-match
747 (concat nonmarker
748 "%%(diary-block \\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\) +"
749 "\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\))\\s-*\\(.*\\)")
750 entry-main)
e0cd68ee 751 (icalendar--dmsg "diary-block %s" entry-main)
707c20a8
GM
752 (let* ((startstring (substring entry-main (match-beginning 1)
753 (match-end 1)))
754 (endstring (substring entry-main (match-beginning 2)
755 (match-end 2)))
e0cd68ee 756 (summary (icalendar--convert-string-for-export
707c20a8
GM
757 (substring entry-main (match-beginning 3)
758 (match-end 3))))
e0cd68ee
GM
759 (startisostring (icalendar--datestring-to-isodate
760 startstring))
761 (endisostring (icalendar--datestring-to-isodate
762 endstring 1)))
707c20a8
GM
763 (setq contents
764 (concat "\nDTSTART;VALUE=DATE:" startisostring
765 "\nDTEND;VALUE=DATE:" endisostring
766 "\nSUMMARY:" summary
767 ))
768 (unless (string= entry-rest "")
769 (setq contents (concat contents "\nDESCRIPTION:"
e0cd68ee 770 (icalendar--convert-string-for-export
707c20a8
GM
771 entry-rest))))))
772 ;; other sexp diary entries -- FIXME
773 ((string-match
774 (concat nonmarker
775 "%%(\\([^)]+\\))\\s-*\\(.*\\)")
776 entry-main)
e0cd68ee 777 (icalendar--dmsg "diary-sexp %s" entry-main)
707c20a8
GM
778 (setq oops t))
779 ;; weekly by day
780 ;; Monday 8:30 Team meeting
781 ((and (string-match
782 (concat nonmarker
783 "\\([a-z]+\\)\\s-+"
784 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
785 "\\(-0?"
786 "\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
787 "\\)?"
788 "\\s-*\\(.*\\)$")
789 entry-main)
e0cd68ee
GM
790 (icalendar--get-weekday-abbrev
791 (substring entry-main (match-beginning 1) (match-end 1))))
792 (icalendar--dmsg "weekly %s" entry-main)
793 (let* ((day (icalendar--get-weekday-abbrev
707c20a8
GM
794 (substring entry-main (match-beginning 1)
795 (match-end 1))))
e0cd68ee 796 (starttimestring (icalendar--diarytime-to-isotime
707c20a8
GM
797 (if (match-beginning 3)
798 (substring entry-main
799 (match-beginning 3)
800 (match-end 3))
801 nil)
802 (if (match-beginning 4)
803 (substring entry-main
804 (match-beginning 4)
805 (match-end 4))
806 nil)))
e0cd68ee 807 (endtimestring (icalendar--diarytime-to-isotime
707c20a8
GM
808 (if (match-beginning 6)
809 (substring entry-main
e0cd68ee 810 (match-beginning 6)
707c20a8
GM
811 (match-end 6))
812 nil)
813 (if (match-beginning 7)
814 (substring entry-main
e0cd68ee 815 (match-beginning 7)
707c20a8
GM
816 (match-end 7))
817 nil)))
e0cd68ee 818 (summary (icalendar--convert-string-for-export
707c20a8
GM
819 (substring entry-main (match-beginning 8)
820 (match-end 8)))))
821 (when starttimestring
822 (unless endtimestring
e0cd68ee
GM
823 (let ((time (read (icalendar--rris "^T0?" ""
824 starttimestring))))
707c20a8
GM
825 (setq endtimestring (format "T%06d" (+ 10000 time))))))
826 (setq contents
827 (concat "\nDTSTART"
828 (if starttimestring "" ";VALUE=DATE")
829 ":19000101" ;; FIXME? Probability that this
830 ;; is the right day is 1/7
831 (or starttimestring "")
832 "\nDTEND"
833 (if endtimestring "" ";VALUE=DATE")
834 ":19000101" ;; FIXME?
835 (or endtimestring "")
836 "\nSUMMARY:" summary
837 "\nRRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=" day
838 )))
839 (unless (string= entry-rest "")
840 (setq contents (concat contents "\nDESCRIPTION:"
e0cd68ee 841 (icalendar--convert-string-for-export
707c20a8
GM
842 entry-rest)))))
843 ;; yearly by day
844 ;; 1 May Tag der Arbeit
845 ((string-match
846 (concat nonmarker
847 (if european-calendar-style
848 "0?\\([1-9]+[0-9]?\\)\\s-+\\([a-z]+\\)\\s-+"
849 "\\([a-z]+\\)\\s-+0?\\([1-9]+[0-9]?\\)\\s-+")
850 "\\*?\\s-*"
851 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
852 "\\("
e0cd68ee
GM
853 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
854 "\\)?"
855 "\\s-*\\([^0-9]+.*\\)$" ; must not match years
707c20a8
GM
856 )
857 entry-main)
e0cd68ee 858 (icalendar--dmsg "yearly %s" entry-main)
707c20a8
GM
859 (let* ((daypos (if european-calendar-style 1 2))
860 (monpos (if european-calendar-style 2 1))
861 (day (read (substring entry-main (match-beginning daypos)
862 (match-end daypos))))
e0cd68ee 863 (month (icalendar--get-month-number
707c20a8
GM
864 (substring entry-main (match-beginning monpos)
865 (match-end monpos))))
e0cd68ee 866 (starttimestring (icalendar--diarytime-to-isotime
707c20a8
GM
867 (if (match-beginning 4)
868 (substring entry-main
869 (match-beginning 4)
870 (match-end 4))
871 nil)
872 (if (match-beginning 5)
873 (substring entry-main
874 (match-beginning 5)
875 (match-end 5))
876 nil)))
e0cd68ee 877 (endtimestring (icalendar--diarytime-to-isotime
707c20a8
GM
878 (if (match-beginning 7)
879 (substring entry-main
e0cd68ee 880 (match-beginning 7)
707c20a8
GM
881 (match-end 7))
882 nil)
883 (if (match-beginning 8)
884 (substring entry-main
e0cd68ee 885 (match-beginning 8)
707c20a8
GM
886 (match-end 8))
887 nil)))
e0cd68ee 888 (summary (icalendar--convert-string-for-export
707c20a8
GM
889 (substring entry-main (match-beginning 9)
890 (match-end 9)))))
891 (when starttimestring
892 (unless endtimestring
e0cd68ee
GM
893 (let ((time (read (icalendar--rris "^T0?" ""
894 starttimestring))))
707c20a8
GM
895 (setq endtimestring (format "T%06d" (+ 10000 time))))))
896 (setq contents
897 (concat "\nDTSTART"
898 (if starttimestring "" ";VALUE=DATE")
899 (format ":1900%02d%02d" month day)
900 (or starttimestring "")
901 "\nDTEND"
902 (if endtimestring "" ";VALUE=DATE")
903 (format ":1900%02d%02d" month day)
904 (or endtimestring "")
905 "\nSUMMARY:" summary
906 "\nRRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH="
907 (format "%2d" month)
908 ";BYMONTHDAY="
909 (format "%2d" day)
910 )))
911 (unless (string= entry-rest "")
912 (setq contents (concat contents "\nDESCRIPTION:"
e0cd68ee 913 (icalendar--convert-string-for-export
707c20a8
GM
914 entry-rest)))))
915 ;; "ordinary" events, start and end time given
916 ;; 1 Feb 2003 Hs Hochzeitsfeier, Dreieich
917 ((string-match
918 (concat nonmarker
919 "\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\)\\s-+"
920 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
921 "\\("
e0cd68ee
GM
922 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
923 "\\)?"
707c20a8
GM
924 "\\s-*\\(.*\\)")
925 entry-main)
e0cd68ee
GM
926 (icalendar--dmsg "ordinary %s" entry-main)
927 (let* ((datestring (icalendar--datestring-to-isodate
707c20a8
GM
928 (substring entry-main (match-beginning 1)
929 (match-end 1))))
e0cd68ee 930 (starttimestring (icalendar--diarytime-to-isotime
707c20a8
GM
931 (if (match-beginning 3)
932 (substring entry-main
933 (match-beginning 3)
934 (match-end 3))
935 nil)
936 (if (match-beginning 4)
937 (substring entry-main
938 (match-beginning 4)
939 (match-end 4))
940 nil)))
e0cd68ee 941 (endtimestring (icalendar--diarytime-to-isotime
707c20a8
GM
942 (if (match-beginning 6)
943 (substring entry-main
e0cd68ee 944 (match-beginning 6)
707c20a8
GM
945 (match-end 6))
946 nil)
947 (if (match-beginning 7)
948 (substring entry-main
e0cd68ee 949 (match-beginning 7)
707c20a8
GM
950 (match-end 7))
951 nil)))
e0cd68ee 952 (summary (icalendar--convert-string-for-export
707c20a8
GM
953 (substring entry-main (match-beginning 8)
954 (match-end 8)))))
955 (when starttimestring
956 (unless endtimestring
e0cd68ee
GM
957 (let ((time (read (icalendar--rris "^T0?" ""
958 starttimestring))))
707c20a8
GM
959 (setq endtimestring (format "T%06d" (+ 10000 time))))))
960 (setq contents (format
e0cd68ee
GM
961 "\nDTSTART%s:%s%s\nDTEND%s:%s%s\nSUMMARY:%s"
962 (if starttimestring "" ";VALUE=DATE")
963 datestring
964 (or starttimestring "")
965 (if endtimestring ""
966 ";VALUE=DATE")
967 datestring
968 (or endtimestring "")
969 summary))
707c20a8
GM
970 (unless (string= entry-rest "")
971 (setq contents (concat contents "\nDESCRIPTION:"
e0cd68ee 972 (icalendar--convert-string-for-export
707c20a8
GM
973 entry-rest))))))
974 ;; everything else
975 (t
976 ;; Oops! what's that?
977 (setq oops t)))
978 (if oops
979 (message "Cannot export entry on line %d"
e0cd68ee 980 (count-lines (point-min) (point)))
707c20a8
GM
981 (setq result (concat result header contents "\nEND:VEVENT"))))
982 ;; we're done, insert everything into the file
983 (let ((coding-system-for-write 'utf8))
984 (set-buffer (find-file ical-filename))
e0cd68ee
GM
985 (goto-char (point-max))
986 (insert "BEGIN:VCALENDAR")
987 (insert "\nPRODID:-//Emacs//NONSGML icalendar.el//EN")
707c20a8
GM
988 (insert "\nVERSION:2.0")
989 (insert result)
990 (insert "\nEND:VCALENDAR\n")))))
991
707c20a8 992;; ======================================================================
e0cd68ee 993;; Import -- convert icalendar to emacs-diary
707c20a8
GM
994;; ======================================================================
995
e0cd68ee 996;; User function
707c20a8 997(defun icalendar-import-file (ical-filename diary-filename
e0cd68ee
GM
998 &optional non-marking)
999 "Import a iCalendar file and append to a diary file.
707c20a8
GM
1000Argument ICAL-FILENAME output iCalendar file.
1001Argument DIARY-FILENAME input `diary-file'.
1002Optional argument NON-MARKING determines whether events are created as
e0cd68ee 1003non-marking or not."
707c20a8 1004 (interactive "fImport iCalendar data from file:
e0cd68ee 1005Finto diary file:
707c20a8
GM
1006p")
1007 ;; clean up the diary file
1008 (save-current-buffer
707c20a8
GM
1009 ;; now load and convert from the ical file
1010 (set-buffer (find-file ical-filename))
e0cd68ee 1011 (icalendar-import-buffer diary-filename t non-marking)))
707c20a8 1012
e0cd68ee
GM
1013;; User function
1014(defun icalendar-import-buffer (&optional diary-file do-not-ask
1015 non-marking)
707c20a8
GM
1016 "Extract iCalendar events from current buffer.
1017
1018This function searches the current buffer for the first iCalendar
1019object, reads it and adds all VEVENT elements to the diary
1020DIARY-FILE.
1021
1022It will ask for each appointment whether to add it to the diary
1023when DO-NOT-ASK is non-nil. When called interactively,
1024DO-NOT-ASK is set to t, so that you are asked fore each event.
1025
1026NON-MARKING determines whether diary events are created as
1027non-marking.
1028
1029This function attempts to notify about problems that occur when
1030reading, parsing, or converting iCalendar data!"
1031 (interactive)
1032 (save-current-buffer
1033 ;; prepare ical
1034 (message "Preparing icalendar...")
e0cd68ee 1035 (set-buffer (icalendar--get-unfolded-buffer (current-buffer)))
707c20a8
GM
1036 (goto-char (point-min))
1037 (message "Preparing icalendar...done")
1038 (if (re-search-forward "^BEGIN:VCALENDAR\\s-*$" nil t)
1039 (let (ical-contents ical-errors)
1040 ;; read ical
1041 (message "Reading icalendar...")
1042 (beginning-of-line)
e0cd68ee 1043 (setq ical-contents (icalendar--read-element nil nil))
707c20a8
GM
1044 (message "Reading icalendar...done")
1045 ;; convert ical
1046 (message "Converting icalendar...")
e0cd68ee 1047 (setq ical-errors (icalendar--convert-ical-to-diary
707c20a8
GM
1048 ical-contents
1049 diary-file do-not-ask non-marking))
1050 (when diary-file
1051 ;; save the diary file
1052 (save-current-buffer
1053 (set-buffer (find-buffer-visiting diary-file))
1054 (save-buffer)))
1055 (message "Converting icalendar...done")
1056 (if (and ical-errors (y-or-n-p
e0cd68ee
GM
1057 (concat "Something went wrong -- "
1058 "do you want to see the "
1059 "error log? ")))
707c20a8
GM
1060 (switch-to-buffer " *icalendar-errors*")))
1061 (message
1062 "Current buffer does not contain icalendar contents!"))))
1063
e0cd68ee
GM
1064(defalias 'icalendar-extract-ical-from-buffer 'icalendar-import-buffer)
1065
1066(make-obsolete 'icalendar-extract-ical-from-buffer 'icalendar-import-buffer
1067 "icalendar 0.07")
1068
1069;; ======================================================================
707c20a8 1070;; private area
e0cd68ee
GM
1071;; ======================================================================
1072
1073(defun icalendar--format-ical-event (event)
707c20a8
GM
1074 "Create a string representation of an iCalendar EVENT."
1075 (let ((string icalendar-import-format)
1076 (conversion-list
1077 '(("%d" DESCRIPTION icalendar-import-format-description)
1078 ("%s" SUMMARY icalendar-import-format-subject)
1079 ("%l" LOCATION icalendar-import-format-location)
1080 ("%o" ORGANIZER icalendar-import-format-organizer))))
1081 ;; convert the specifiers in the format string
1082 (mapcar (lambda (i)
1083 (let* ((spec (car i))
1084 (prop (cadr i))
1085 (format (car (cddr i)))
e0cd68ee 1086 (contents (icalendar--get-event-property event prop))
707c20a8 1087 (formatted-contents ""))
707c20a8
GM
1088 (when (and contents (> (length contents) 0))
1089 (setq formatted-contents
e0cd68ee
GM
1090 (icalendar--rris "%s"
1091 (icalendar--convert-string-for-import
1092 contents)
1093 (symbol-value format))))
1094 (setq string (icalendar--rris spec
1095 formatted-contents
1096 string))))
707c20a8
GM
1097 conversion-list)
1098 string))
1099
e0cd68ee
GM
1100(defun icalendar--convert-ical-to-diary (ical-list diary-file
1101 &optional do-not-ask
1102 non-marking)
707c20a8
GM
1103 "Convert an iCalendar file to an Emacs diary file.
1104Import VEVENTS from the iCalendar object ICAL-LIST and saves them to a
1105DIARY-FILE. If DO-NOT-ASK is nil the user is asked for each event
1106whether to actually import it. NON-MARKING determines whether diary
1107events are created as non-marking.
1108This function attempts to return t if something goes wrong. In this
1109case an error string which describes all the errors and problems is
1110written into the buffer ` *icalendar-errors*'."
e0cd68ee 1111 (let* ((ev (icalendar--all-events ical-list))
707c20a8
GM
1112 (error-string "")
1113 (event-ok t)
1114 (found-error nil)
1115 e diary-string)
1116 ;; step through all events/appointments
1117 (while ev
1118 (setq e (car ev))
1119 (setq ev (cdr ev))
1120 (setq event-ok nil)
1121 (condition-case error-val
e0cd68ee
GM
1122 (let* ((dtstart (icalendar--decode-isodatetime
1123 (icalendar--get-event-property e 'DTSTART)))
707c20a8 1124 (start-d (calendar-date-string
e0cd68ee
GM
1125 (icalendar--datetime-to-noneuropean-date
1126 dtstart)
707c20a8 1127 t t))
e0cd68ee
GM
1128 (start-t (icalendar--datetime-to-colontime dtstart))
1129 (dtend (icalendar--decode-isodatetime
1130 (icalendar--get-event-property e 'DTEND)))
707c20a8
GM
1131 end-d
1132 end-t
e0cd68ee
GM
1133 (subject (icalendar--convert-string-for-import
1134 (or (icalendar--get-event-property e 'SUMMARY)
707c20a8 1135 "No Subject")))
e0cd68ee
GM
1136 (rrule (icalendar--get-event-property e 'RRULE))
1137 (rdate (icalendar--get-event-property e 'RDATE))
1138 (duration (icalendar--get-event-property e 'DURATION)))
1139 (icalendar--dmsg "%s: %s" start-d subject)
707c20a8 1140 (when duration
e0cd68ee 1141 (let ((dtend2 (icalendar--add-decoded-times
707c20a8 1142 dtstart
e0cd68ee 1143 (icalendar--decode-isoduration duration))))
707c20a8
GM
1144 (if (and dtend (not (eq dtend dtend2)))
1145 (message "Inconsistent endtime and duration for %s"
1146 subject))
1147 (setq dtend dtend2)))
1148 (setq end-d (if dtend
1149 (calendar-date-string
e0cd68ee
GM
1150 (icalendar--datetime-to-noneuropean-date
1151 dtend)
707c20a8
GM
1152 t t)
1153 start-d))
1154 (setq end-t (if dtend
e0cd68ee 1155 (icalendar--datetime-to-colontime dtend)
707c20a8 1156 start-t))
e0cd68ee 1157 (icalendar--dmsg "start-d: %s, end-d: %s" start-d end-d)
707c20a8
GM
1158 (cond
1159 ;; recurring event
1160 (rrule
e0cd68ee
GM
1161 (icalendar--dmsg "recurring event")
1162 (let* ((rrule-props (icalendar--split-value rrule))
707c20a8
GM
1163 (frequency (car (cdr (assoc 'FREQ rrule-props))))
1164 (until (car (cdr (assoc 'UNTIL rrule-props))))
1165 (interval (read (car (cdr (assoc 'INTERVAL
e0cd68ee 1166 rrule-props))))))
707c20a8
GM
1167 (cond ((string-equal frequency "WEEKLY")
1168 (if (not start-t)
1169 (progn
1170 ;; weekly and all-day
e0cd68ee 1171 (icalendar--dmsg "weekly all-day")
707c20a8
GM
1172 (setq diary-string
1173 (format
e0cd68ee
GM
1174 "%%%%(diary-cyclic %d %s)"
1175 (* interval 7)
1176 (icalendar--datetime-to-european-date
1177 dtstart))))
707c20a8
GM
1178 ;; weekly and not all-day
1179 (let* ((byday (cadr (assoc 'BYDAY rrule-props)))
1180 (weekday
f2aa5449 1181 (icalendar--get-weekday-number byday)))
e0cd68ee 1182 (icalendar--dmsg "weekly not-all-day")
f2aa5449 1183 (if (> weekday -1)
707c20a8 1184 (setq diary-string
f2aa5449
GM
1185 (format "%s %s%s%s"
1186 (aref calendar-day-name-array
1187 weekday)
707c20a8
GM
1188 start-t (if end-t "-" "")
1189 (or end-t "")))
1190 ;; FIXME!!!!
1191 ;; DTSTART;VALUE=DATE-TIME:20030919T090000
1192 ;; DTEND;VALUE=DATE-TIME:20030919T113000
1193 (setq diary-string
1194 (format
e0cd68ee
GM
1195 "%%%%(diary-cyclic %s %s) %s%s%s"
1196 (* interval 7)
1197 (icalendar--datetime-to-european-date
1198 dtstart)
1199 start-t (if end-t "-" "") (or end-t ""))))
707c20a8
GM
1200 (setq event-ok t))))
1201 ;; yearly
1202 ((string-equal frequency "YEARLY")
e0cd68ee 1203 (icalendar--dmsg "yearly")
707c20a8
GM
1204 (setq diary-string
1205 (format
e0cd68ee
GM
1206 "%%%%(diary-anniversary %s)"
1207 (icalendar--datetime-to-european-date dtstart)))
707c20a8
GM
1208 (setq event-ok t))
1209 ;; FIXME: war auskommentiert:
1210 ((and (string-equal frequency "DAILY")
1211 ;;(not (string= start-d end-d))
1212 ;;(not start-t)
1213 ;;(not end-t)
1214 )
e0cd68ee
GM
1215 (let ((ds (icalendar--datetime-to-noneuropean-date
1216 (icalendar--decode-isodatetime
1217 (icalendar--get-event-property e
1218 'DTSTART))))
1219 (de (icalendar--datetime-to-noneuropean-date
1220 (icalendar--decode-isodatetime
707c20a8
GM
1221 until))))
1222 (setq diary-string
1223 (format
e0cd68ee
GM
1224 "%%%%(diary-block %d %d %d %d %d %d)"
1225 (nth 1 ds) (nth 0 ds) (nth 2 ds)
1226 (nth 1 de) (nth 0 de) (nth 2 de))))
707c20a8
GM
1227 (setq event-ok t)))
1228 ))
1229 (rdate
e0cd68ee 1230 (icalendar--dmsg "rdate event")
707c20a8
GM
1231 (setq diary-string "")
1232 (mapcar (lambda (datestring)
1233 (setq diary-string
1234 (concat diary-string
1235 (format "......"))))
e0cd68ee 1236 (icalendar--split-value rdate)))
707c20a8
GM
1237 ;; non-recurring event
1238 ;; long event
1239 ((not (string= start-d end-d))
e0cd68ee
GM
1240 (icalendar--dmsg "non-recurring event")
1241 (let ((ds (icalendar--datetime-to-noneuropean-date dtstart))
1242 (de (icalendar--datetime-to-noneuropean-date dtend)))
707c20a8
GM
1243 (setq diary-string
1244 (format "%%%%(diary-block %d %d %d %d %d %d)"
1245 (nth 1 ds) (nth 0 ds) (nth 2 ds)
1246 (nth 1 de) (nth 0 de) (nth 2 de))))
1247 (setq event-ok t))
1248 ;; not all-day
1249 ((and start-t (or (not end-t)
1250 (not (string= start-t end-t))))
e0cd68ee 1251 (icalendar--dmsg "not all day event")
707c20a8
GM
1252 (cond (end-t
1253 (setq diary-string (format "%s %s-%s" start-d
e0cd68ee 1254 start-t end-t)))
707c20a8
GM
1255 (t
1256 (setq diary-string (format "%s %s" start-d
e0cd68ee 1257 start-t))))
707c20a8
GM
1258 (setq event-ok t))
1259 ;; all-day event
1260 (t
e0cd68ee 1261 (icalendar--dmsg "all day event")
707c20a8
GM
1262 (setq diary-string start-d)
1263 (setq event-ok t)))
1264 ;; add all other elements unless the user doesn't want to have
1265 ;; them
1266 (if event-ok
1267 (progn
1268 (setq diary-string
e0cd68ee
GM
1269 (concat diary-string " "
1270 (icalendar--format-ical-event e)))
707c20a8 1271 (if do-not-ask (setq subject nil))
e0cd68ee
GM
1272 (icalendar--add-diary-entry diary-string diary-file
1273 non-marking subject))
707c20a8
GM
1274 ;; event was not ok
1275 (setq found-error t)
1276 (setq error-string
e0cd68ee
GM
1277 (format "%s\nCannot handle this event:%s"
1278 error-string e))))
707c20a8
GM
1279 ;; handle errors
1280 (error
1281 (message "Ignoring event \"%s\"" e)
1282 (setq found-error t)
1283 (setq error-string (format "%s\nCannot handle this event: %s"
1284 error-string e)))))
1285 (if found-error
1286 (save-current-buffer
1287 (set-buffer (get-buffer-create " *icalendar-errors*"))
1288 (erase-buffer)
1289 (insert error-string)))
1290 (message "Converting icalendar...done")
1291 found-error))
1292
e0cd68ee
GM
1293(defun icalendar--add-diary-entry (string diary-file non-marking
1294 &optional subject)
707c20a8
GM
1295 "Add STRING to the diary file DIARY-FILE.
1296STRING must be a properly formatted valid diary entry. NON-MARKING
1297determines whether diary events are created as non-marking. If
1298SUBJECT is not nil it must be a string that gives the subject of the
1299entry. In this case the user will be asked whether he wants to insert
1300the entry."
e0cd68ee 1301 (when (or (not subject) ;
707c20a8 1302 (y-or-n-p (format "Add appointment for `%s' to diary? "
e0cd68ee 1303 subject)))
707c20a8
GM
1304 (when subject
1305 (setq non-marking
1306 (y-or-n-p (format "Make appointment non-marking? "))))
1307 (save-window-excursion
1308 (unless diary-file
1309 (setq diary-file
1310 (read-file-name "Add appointment to this diary file: ")))
1311 (make-diary-entry string non-marking diary-file))))
1312
707c20a8
GM
1313(provide 'icalendar)
1314
a13bc064 1315;; arch-tag: 74fdbe8e-0451-4e38-bb61-4416e822f4fc
707c20a8 1316;;; icalendar.el ends here