]> code.delx.au - gnu-emacs/blob - lisp/calendar/icalendar.el
From Ulf Jasper <ulf.jasper@web.de>:
[gnu-emacs] / lisp / calendar / icalendar.el
1 ;;; icalendar.el --- iCalendar implementation -*-coding: utf-8 -*-
2
3 ;; Copyright (C) 2002, 2003, 2004 Free Software Foundation, Inc.
4
5 ;; Author: Ulf Jasper <ulf.jasper@web.de>
6 ;; Created: August 2002
7 ;; Keywords: calendar
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
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.
42 ;; I18n-problems fixed -- use calendar-(month|day)-name-array.
43 ;; Fixed problems with export of multi-line diary entries.
44
45 ;; 0.06: Bugfixes regarding icalendar-import-format-*.
46 ;; Fix in icalendar-convert-diary-to-ical -- thanks to Philipp
47 ;; Grau.
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
76 ;; * Import from ical to diary:
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
88 ;; * Export from diary to ical
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
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?
98
99
100 ;;; Code:
101
102 (defconst icalendar-version 0.07
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.
116 This string defines how iCalendar events are inserted into diary
117 file. 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.
128 This applies only if the subject is not empty! `%s' is replaced
129 by 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.
136 This applies only if the description is not empty! `%s' is
137 replaced 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.
144 This applies only if the location is not empty! `%s' is replaced
145 by 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.
152 This applies only if the organizer is not empty! `%s' is
153 replaced by the organizer."
154 :type 'string
155 :group 'icalendar)
156
157 (defcustom icalendar-duration-correction
158 t
159 "Workaround for all-day events.
160 If non-nil the length=duration of iCalendar appointments that
161 have a length of exactly n days is decreased by one day. This
162 fixes problems with all-day events, which appear to be one day
163 longer than they are."
164 :type 'boolean
165 :group 'icalendar)
166
167
168 ;; ======================================================================
169 ;; NO USER SERVICABLE PARTS BELOW THIS LINE
170 ;; ======================================================================
171
172 (defconst icalendar--weekday-array ["SU" "MO" "TU" "WE" "TH" "FR" "SA"])
173
174 (defvar icalendar-debug nil ".")
175
176 ;; ======================================================================
177 ;; all the other libs we need
178 ;; ======================================================================
179 (require 'calendar)
180 (require 'appt)
181
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
190 ;; ======================================================================
191 ;; Core functionality
192 ;; Functions for parsing icalendars, importing and so on
193 ;; ======================================================================
194
195 (defun icalendar--get-unfolded-buffer (folded-ical-buffer)
196 "Return a new buffer containing the unfolded contents of a buffer.
197 Folding is the iCalendar way of wrapping long lines. In the
198 created buffer all occurrences of CR LF BLANK are replaced by the
199 empty string. Argument FOLDED-ICAL-BUFFER is the unfolded input
200 buffer."
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)
207 (replace-match "" nil nil)))
208 unfolded-buffer))
209
210 (defsubst icalendar--rris (re rp st)
211 "Replace regexp RE with RP in string ST and return the new string.
212 This is here for compatibility with XEmacs."
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
220 (defun icalendar--read-element (invalue inparams)
221 "Recursively read the next iCalendar element in the current buffer.
222 INVALUE gives the current iCalendar element we are reading.
223 INPARAMS gives the current parameters.....
224 This function calls itself recursively for each nested calendar element
225 it finds"
226 (let (element children line name params param param-name param-value
227 value
228 (continue t))
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)
256 (setq value (icalendar--rris "\r?\n[ \t]" "" (match-string 0)))
257 (setq line (list name params value))
258 (cond ((eq name 'BEGIN)
259 (setq children
260 (append children
261 (list (icalendar--read-element (intern value)
262 params)))))
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
275 ;;(defsubst icalendar--get-all-event-properties (event)
276 ;; "Return the list of properties in this EVENT."
277 ;; (car (cddr event)))
278
279 (defun icalendar--get-event-property (event prop)
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
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)
305 "Return all children of the given NODE which have a name NAME.
306 For instance the VCALENDAR node can have VEVENT children as well as VTODO
307 children."
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
316 (mapcar (lambda (n)
317 (icalendar--get-children n name))
318 children))))
319 (if subresult
320 (if result
321 (setq result (append result subresult))
322 (setq result subresult)))))
323 result))
324
325 ; private
326 (defun icalendar--all-events (icalendar)
327 "Return the list of all existing events in the given ICALENDAR."
328 (icalendar--get-children (car icalendar) 'VEVENT))
329
330 (defun icalendar--split-value (value-string)
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
342 (re-search-forward
343 "\\([A-Za-z0-9-]+\\)=\\(\\([^;,:]+\\)\\|\"\\([^\"]+\\)\"\\);?"
344 nil t)
345 (setq param-name (intern (match-string 1)))
346 (setq param-value (match-string 2))
347 (setq result
348 (append result (list (list param-name param-value)))))))
349 result))
350
351 (defun icalendar--decode-isodatetime (isodatetimestring)
352 "Return ISODATETIMESTRING in format like `decode-time'.
353 Converts from ISO-8601 to Emacs representation. If ISODATETIMESTRING
354 specifies UTC time (trailing letter Z) the decoded time is given in
355 the local time zone! FIXME: TZID-attributes are ignored....! FIXME:
356 multiple comma-separated values should be allowed!"
357 (icalendar--dmsg isodatetimestring)
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)
367 ;; hour/minute present
368 (setq hour (read (substring isodatetimestring 9 11)))
369 (setq minute (read (substring isodatetimestring 11 13))))
370 (when (> (length isodatetimestring) 14)
371 ;; seconds present
372 (setq second (read (substring isodatetimestring 13 15))))
373 (when (and (> (length isodatetimestring) 15)
374 ;; UTC specifier present
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
389 (defun icalendar--decode-isoduration (isodurationstring)
390 "Return ISODURATIONSTRING in format like `decode-time'.
391 Converts from ISO-8601 to Emacs representation. If ISODURATIONSTRING
392 specifies UTC time (trailing letter Z) the decoded time is given in
393 the local time zone! FIXME: TZID-attributes are ignored....! FIXME:
394 multiple 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
403 "\\(\\([0-9]+\\)M\\)?\\(\\([0-9]+\\)S\\)?\\)" ; mand. time
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))
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)))
444 ;; isodatetimestring == nil
445 nil))
446
447 (defun icalendar--add-decoded-times (time1 time2)
448 "Add TIME1 to TIME2.
449 Both times must be given in decoded form. One of these times must be
450 valid (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
464 (defun icalendar--datetime-to-noneuropean-date (datetime)
465 "Convert the decoded DATETIME to non-european-style format.
466 Non-European format: (month day year)."
467 (if datetime
468 (list (nth 4 datetime) ;month
469 (nth 3 datetime) ;day
470 (nth 5 datetime)) ;year
471 ;; datetime == nil
472 nil))
473
474 (defun icalendar--datetime-to-european-date (datetime)
475 "Convert the decoded DATETIME to European format.
476 European format: (day month year).
477 FIXME"
478 (if datetime
479 (format "%d %d %d" (nth 3 datetime) ; day
480 (nth 4 datetime) ;month
481 (nth 5 datetime)) ;year
482 ;; datetime == nil
483 nil))
484
485 (defun icalendar--datetime-to-colontime (datetime)
486 "Extract the time part of a decoded DATETIME into 24-hour format.
487 Note that this silently ignores seconds."
488 (format "%02d:%02d" (nth 2 datetime) (nth 1 datetime)))
489
490 (defun icalendar--get-month-number (monthname)
491 "Return the month number for the given MONTHNAME."
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))
518
519 (defun icalendar--get-weekday-abbrev (weekday)
520 "Return the abbreviated WEEKDAY."
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 "??"))
533
534 (defun icalendar--datestring-to-isodate (datestring &optional day-shift)
535 "Convert diary-style DATESTRING to iso-style date.
536 If 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
538 takes care of european-style."
539 (let ((day -1) month year)
540 (save-match-data
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)))
587 (if (> day 0)
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)))
593 nil)))
594
595 (defun icalendar--diarytime-to-isotime (timestring ampmstring)
596 "Convert a a time like 9:30pm to an iso-conform string like T213000.
597 In this example the TIMESTRING would be \"9:30\" and the AMPMSTRING
598 would be \"pm\"."
599 (if timestring
600 (let ((starttimenum (read (icalendar--rris ":" "" timestring))))
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
607 (defun icalendar--convert-string-for-export (s)
608 "Escape comma and other critical characters in string S."
609 (icalendar--rris "," "\\\\," s))
610
611 (defun icalendar--convert-string-for-import (string)
612 "Remove escape chars for comma, semicolon etc. from STRING."
613 (icalendar--rris
614 "\\\\n" "\n " (icalendar--rris
615 "\\\\\"" "\"" (icalendar--rris
616 "\\\\;" ";" (icalendar--rris
617 "\\\\," "," string)))))
618
619 ;; ======================================================================
620 ;; Export -- convert emacs-diary to icalendar
621 ;; ======================================================================
622
623 ;; User function
624 (defun icalendar-export-file (diary-filename ical-filename)
625 "Export diary file to iCalendar format.
626 All diary entries in the file DIARY-FILENAME are converted to iCalendar
627 format. The result is appended to the file ICAL-FILENAME."
628 (interactive "FExport diary data from file:
629 Finto iCalendar file: ")
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.
641 All diary entries in the region from MIN to MAX in the current buffer are
642 converted to iCalendar format. The result is appended to the file
643 ICAL-FILENAME."
644 (interactive "r
645 FExport diary data into iCalendar file: ")
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)
654 "?")))
655 (save-excursion
656 (goto-char min)
657 (while (re-search-forward
658 "^\\([^ \t\n].*\\)\\(\\(\n[ \t].*\\)*\\)" max t)
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)
674 (icalendar--dmsg "diary-anniversary %s" entry-main)
675 (let* ((datetime (substring entry-main (match-beginning 1)
676 (match-end 1)))
677 (summary (icalendar--convert-string-for-export
678 (substring entry-main (match-beginning 2)
679 (match-end 2))))
680 (startisostring (icalendar--datestring-to-isodate
681 datetime))
682 (endisostring (icalendar--datestring-to-isodate
683 datetime 1)))
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:"
698 (icalendar--convert-string-for-export
699 entry-rest)))))
700 ;; cyclic events
701 ;; %%(diary-cyclic )
702 ((string-match
703 (concat nonmarker
704 "%%(diary-cyclic \\([^ ]+\\) +"
705 "\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\))\\s-*\\(.*\\)")
706 entry-main)
707 (icalendar--dmsg "diary-cyclic %s" entry-main)
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)))
712 (summary (icalendar--convert-string-for-export
713 (substring entry-main (match-beginning 3)
714 (match-end 3))))
715 (startisostring (icalendar--datestring-to-isodate
716 datetime))
717 (endisostring (icalendar--datestring-to-isodate
718 datetime 1)))
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:"
729 (icalendar--convert-string-for-export
730 entry-rest)))))
731 ;; diary-date -- FIXME
732 ((string-match
733 (concat nonmarker
734 "%%(diary-date \\([^)]+\\))\\s-*\\(.*\\)")
735 entry-main)
736 (icalendar--dmsg "diary-date %s" entry-main)
737 (setq oops t))
738 ;; float events -- FIXME
739 ((string-match
740 (concat nonmarker
741 "%%(diary-float \\([^)]+\\))\\s-*\\(.*\\)")
742 entry-main)
743 (icalendar--dmsg "diary-float %s" entry-main)
744 (setq oops t))
745 ;; block events
746 ((string-match
747 (concat nonmarker
748 "%%(diary-block \\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\) +"
749 "\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\))\\s-*\\(.*\\)")
750 entry-main)
751 (icalendar--dmsg "diary-block %s" entry-main)
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)))
756 (summary (icalendar--convert-string-for-export
757 (substring entry-main (match-beginning 3)
758 (match-end 3))))
759 (startisostring (icalendar--datestring-to-isodate
760 startstring))
761 (endisostring (icalendar--datestring-to-isodate
762 endstring 1)))
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:"
770 (icalendar--convert-string-for-export
771 entry-rest))))))
772 ;; other sexp diary entries -- FIXME
773 ((string-match
774 (concat nonmarker
775 "%%(\\([^)]+\\))\\s-*\\(.*\\)")
776 entry-main)
777 (icalendar--dmsg "diary-sexp %s" entry-main)
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)
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
794 (substring entry-main (match-beginning 1)
795 (match-end 1))))
796 (starttimestring (icalendar--diarytime-to-isotime
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)))
807 (endtimestring (icalendar--diarytime-to-isotime
808 (if (match-beginning 6)
809 (substring entry-main
810 (match-beginning 6)
811 (match-end 6))
812 nil)
813 (if (match-beginning 7)
814 (substring entry-main
815 (match-beginning 7)
816 (match-end 7))
817 nil)))
818 (summary (icalendar--convert-string-for-export
819 (substring entry-main (match-beginning 8)
820 (match-end 8)))))
821 (when starttimestring
822 (unless endtimestring
823 (let ((time (read (icalendar--rris "^T0?" ""
824 starttimestring))))
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:"
841 (icalendar--convert-string-for-export
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 "\\("
853 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
854 "\\)?"
855 "\\s-*\\([^0-9]+.*\\)$" ; must not match years
856 )
857 entry-main)
858 (icalendar--dmsg "yearly %s" entry-main)
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))))
863 (month (icalendar--get-month-number
864 (substring entry-main (match-beginning monpos)
865 (match-end monpos))))
866 (starttimestring (icalendar--diarytime-to-isotime
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)))
877 (endtimestring (icalendar--diarytime-to-isotime
878 (if (match-beginning 7)
879 (substring entry-main
880 (match-beginning 7)
881 (match-end 7))
882 nil)
883 (if (match-beginning 8)
884 (substring entry-main
885 (match-beginning 8)
886 (match-end 8))
887 nil)))
888 (summary (icalendar--convert-string-for-export
889 (substring entry-main (match-beginning 9)
890 (match-end 9)))))
891 (when starttimestring
892 (unless endtimestring
893 (let ((time (read (icalendar--rris "^T0?" ""
894 starttimestring))))
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:"
913 (icalendar--convert-string-for-export
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 "\\("
922 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
923 "\\)?"
924 "\\s-*\\(.*\\)")
925 entry-main)
926 (icalendar--dmsg "ordinary %s" entry-main)
927 (let* ((datestring (icalendar--datestring-to-isodate
928 (substring entry-main (match-beginning 1)
929 (match-end 1))))
930 (starttimestring (icalendar--diarytime-to-isotime
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)))
941 (endtimestring (icalendar--diarytime-to-isotime
942 (if (match-beginning 6)
943 (substring entry-main
944 (match-beginning 6)
945 (match-end 6))
946 nil)
947 (if (match-beginning 7)
948 (substring entry-main
949 (match-beginning 7)
950 (match-end 7))
951 nil)))
952 (summary (icalendar--convert-string-for-export
953 (substring entry-main (match-beginning 8)
954 (match-end 8)))))
955 (when starttimestring
956 (unless endtimestring
957 (let ((time (read (icalendar--rris "^T0?" ""
958 starttimestring))))
959 (setq endtimestring (format "T%06d" (+ 10000 time))))))
960 (setq contents (format
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))
970 (unless (string= entry-rest "")
971 (setq contents (concat contents "\nDESCRIPTION:"
972 (icalendar--convert-string-for-export
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"
980 (count-lines (point-min) (point)))
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))
985 (goto-char (point-max))
986 (insert "BEGIN:VCALENDAR")
987 (insert "\nPRODID:-//Emacs//NONSGML icalendar.el//EN")
988 (insert "\nVERSION:2.0")
989 (insert result)
990 (insert "\nEND:VCALENDAR\n")))))
991
992 ;; ======================================================================
993 ;; Import -- convert icalendar to emacs-diary
994 ;; ======================================================================
995
996 ;; User function
997 (defun icalendar-import-file (ical-filename diary-filename
998 &optional non-marking)
999 "Import a iCalendar file and append to a diary file.
1000 Argument ICAL-FILENAME output iCalendar file.
1001 Argument DIARY-FILENAME input `diary-file'.
1002 Optional argument NON-MARKING determines whether events are created as
1003 non-marking or not."
1004 (interactive "fImport iCalendar data from file:
1005 Finto diary file:
1006 p")
1007 ;; clean up the diary file
1008 (save-current-buffer
1009 ;; now load and convert from the ical file
1010 (set-buffer (find-file ical-filename))
1011 (icalendar-import-buffer diary-filename t non-marking)))
1012
1013 ;; User function
1014 (defun icalendar-import-buffer (&optional diary-file do-not-ask
1015 non-marking)
1016 "Extract iCalendar events from current buffer.
1017
1018 This function searches the current buffer for the first iCalendar
1019 object, reads it and adds all VEVENT elements to the diary
1020 DIARY-FILE.
1021
1022 It will ask for each appointment whether to add it to the diary
1023 when DO-NOT-ASK is non-nil. When called interactively,
1024 DO-NOT-ASK is set to t, so that you are asked fore each event.
1025
1026 NON-MARKING determines whether diary events are created as
1027 non-marking.
1028
1029 This function attempts to notify about problems that occur when
1030 reading, parsing, or converting iCalendar data!"
1031 (interactive)
1032 (save-current-buffer
1033 ;; prepare ical
1034 (message "Preparing icalendar...")
1035 (set-buffer (icalendar--get-unfolded-buffer (current-buffer)))
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)
1043 (setq ical-contents (icalendar--read-element nil nil))
1044 (message "Reading icalendar...done")
1045 ;; convert ical
1046 (message "Converting icalendar...")
1047 (setq ical-errors (icalendar--convert-ical-to-diary
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
1057 (concat "Something went wrong -- "
1058 "do you want to see the "
1059 "error log? ")))
1060 (switch-to-buffer " *icalendar-errors*")))
1061 (message
1062 "Current buffer does not contain icalendar contents!"))))
1063
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 ;; ======================================================================
1070 ;; private area
1071 ;; ======================================================================
1072
1073 (defun icalendar--format-ical-event (event)
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)))
1086 (contents (icalendar--get-event-property event prop))
1087 (formatted-contents ""))
1088 (when (and contents (> (length contents) 0))
1089 (setq formatted-contents
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))))
1097 conversion-list)
1098 string))
1099
1100 (defun icalendar--convert-ical-to-diary (ical-list diary-file
1101 &optional do-not-ask
1102 non-marking)
1103 "Convert an iCalendar file to an Emacs diary file.
1104 Import VEVENTS from the iCalendar object ICAL-LIST and saves them to a
1105 DIARY-FILE. If DO-NOT-ASK is nil the user is asked for each event
1106 whether to actually import it. NON-MARKING determines whether diary
1107 events are created as non-marking.
1108 This function attempts to return t if something goes wrong. In this
1109 case an error string which describes all the errors and problems is
1110 written into the buffer ` *icalendar-errors*'."
1111 (let* ((ev (icalendar--all-events ical-list))
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
1122 (let* ((dtstart (icalendar--decode-isodatetime
1123 (icalendar--get-event-property e 'DTSTART)))
1124 (start-d (calendar-date-string
1125 (icalendar--datetime-to-noneuropean-date
1126 dtstart)
1127 t t))
1128 (start-t (icalendar--datetime-to-colontime dtstart))
1129 (dtend (icalendar--decode-isodatetime
1130 (icalendar--get-event-property e 'DTEND)))
1131 end-d
1132 end-t
1133 (subject (icalendar--convert-string-for-import
1134 (or (icalendar--get-event-property e 'SUMMARY)
1135 "No Subject")))
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)
1140 (when duration
1141 (let ((dtend2 (icalendar--add-decoded-times
1142 dtstart
1143 (icalendar--decode-isoduration duration))))
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
1150 (icalendar--datetime-to-noneuropean-date
1151 dtend)
1152 t t)
1153 start-d))
1154 (setq end-t (if dtend
1155 (icalendar--datetime-to-colontime dtend)
1156 start-t))
1157 (icalendar--dmsg "start-d: %s, end-d: %s" start-d end-d)
1158 (cond
1159 ;; recurring event
1160 (rrule
1161 (icalendar--dmsg "recurring event")
1162 (let* ((rrule-props (icalendar--split-value rrule))
1163 (frequency (car (cdr (assoc 'FREQ rrule-props))))
1164 (until (car (cdr (assoc 'UNTIL rrule-props))))
1165 (interval (read (car (cdr (assoc 'INTERVAL
1166 rrule-props))))))
1167 (cond ((string-equal frequency "WEEKLY")
1168 (if (not start-t)
1169 (progn
1170 ;; weekly and all-day
1171 (icalendar--dmsg "weekly all-day")
1172 (setq diary-string
1173 (format
1174 "%%%%(diary-cyclic %d %s)"
1175 (* interval 7)
1176 (icalendar--datetime-to-european-date
1177 dtstart))))
1178 ;; weekly and not all-day
1179 (let* ((byday (cadr (assoc 'BYDAY rrule-props)))
1180 (weekday
1181 (icalendar--get-weekday-number byday)))
1182 (icalendar--dmsg "weekly not-all-day")
1183 (if (> weekday -1)
1184 (setq diary-string
1185 (format "%s %s%s%s"
1186 (aref calendar-day-name-array
1187 weekday)
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
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 ""))))
1200 (setq event-ok t))))
1201 ;; yearly
1202 ((string-equal frequency "YEARLY")
1203 (icalendar--dmsg "yearly")
1204 (setq diary-string
1205 (format
1206 "%%%%(diary-anniversary %s)"
1207 (icalendar--datetime-to-european-date dtstart)))
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 )
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
1221 until))))
1222 (setq diary-string
1223 (format
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))))
1227 (setq event-ok t)))
1228 ))
1229 (rdate
1230 (icalendar--dmsg "rdate event")
1231 (setq diary-string "")
1232 (mapcar (lambda (datestring)
1233 (setq diary-string
1234 (concat diary-string
1235 (format "......"))))
1236 (icalendar--split-value rdate)))
1237 ;; non-recurring event
1238 ;; long event
1239 ((not (string= start-d end-d))
1240 (icalendar--dmsg "non-recurring event")
1241 (let ((ds (icalendar--datetime-to-noneuropean-date dtstart))
1242 (de (icalendar--datetime-to-noneuropean-date dtend)))
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))))
1251 (icalendar--dmsg "not all day event")
1252 (cond (end-t
1253 (setq diary-string (format "%s %s-%s" start-d
1254 start-t end-t)))
1255 (t
1256 (setq diary-string (format "%s %s" start-d
1257 start-t))))
1258 (setq event-ok t))
1259 ;; all-day event
1260 (t
1261 (icalendar--dmsg "all day event")
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
1269 (concat diary-string " "
1270 (icalendar--format-ical-event e)))
1271 (if do-not-ask (setq subject nil))
1272 (icalendar--add-diary-entry diary-string diary-file
1273 non-marking subject))
1274 ;; event was not ok
1275 (setq found-error t)
1276 (setq error-string
1277 (format "%s\nCannot handle this event:%s"
1278 error-string e))))
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
1293 (defun icalendar--add-diary-entry (string diary-file non-marking
1294 &optional subject)
1295 "Add STRING to the diary file DIARY-FILE.
1296 STRING must be a properly formatted valid diary entry. NON-MARKING
1297 determines whether diary events are created as non-marking. If
1298 SUBJECT is not nil it must be a string that gives the subject of the
1299 entry. In this case the user will be asked whether he wants to insert
1300 the entry."
1301 (when (or (not subject) ;
1302 (y-or-n-p (format "Add appointment for `%s' to diary? "
1303 subject)))
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
1313 (provide 'icalendar)
1314
1315 ;; arch-tag: 74fdbe8e-0451-4e38-bb61-4416e822f4fc
1316 ;;; icalendar.el ends here