(Frename_file): Undo last change: no need to ifdef away
[bpt/emacs.git] / lisp / calendar / icalendar.el
CommitLineData
e0cd68ee 1;;; icalendar.el --- iCalendar implementation -*-coding: utf-8 -*-
707c20a8 2
74692b14 3;; Copyright (C) 2002, 2003, 2004, 2005 Free Software Foundation, Inc.
707c20a8 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
9dd9ed20
GM
31;; Please note:
32;; - Diary entries which have a start time but no end time are assumed to
33;; last for one hour when they are exported.
34;; - Weekly diary entries are assumed to occur the first time in the first
35;; week of the year 2000 when they are exported.
36;; - Yearly diary entries are assumed to occur the first time in the year
37;; 1900 when they are exported.
707c20a8
GM
38
39;;; History:
40
81d56594 41;; 0.07 onwards: see lisp/ChangeLog
e0cd68ee
GM
42
43;; 0.06: Bugfixes regarding icalendar-import-format-*.
44;; Fix in icalendar-convert-diary-to-ical -- thanks to Philipp
45;; Grau.
707c20a8
GM
46
47;; 0.05: New import format scheme: Replaced icalendar-import-prefix-*,
48;; icalendar-import-ignored-properties, and
49;; icalendar-import-separator with icalendar-import-format(-*).
50;; icalendar-import-file and icalendar-convert-diary-to-ical
51;; have an extra parameter which should prevent them from
52;; erasing their target files (untested!).
53;; Tested with Emacs 21.3.2
54
55;; 0.04: Bugfix: import: double quoted param values did not work
56;; Read DURATION property when importing.
57;; Added parameter icalendar-duration-correction.
58
59;; 0.03: Export takes care of european-calendar-style.
60;; Tested with Emacs 21.3.2 and XEmacs 21.4.12
61
62;; 0.02: Should work in XEmacs now. Thanks to Len Trigg for the
63;; XEmacs patches!
64;; Added exporting from Emacs diary to ical.
65;; Some bugfixes, after testing with calendars from
66;; http://icalshare.com.
67;; Tested with Emacs 21.3.2 and XEmacs 21.4.12
68
69;; 0.01: First published version. Trial version. Alpha version.
70
71;; ======================================================================
72;; To Do:
73
e0cd68ee 74;; * Import from ical to diary:
707c20a8
GM
75;; + Need more properties for icalendar-import-format
76;; + check vcalendar version
77;; + check (unknown) elements
78;; + recurring events!
79;; + works for european style calendars only! Does it?
80;; + alarm
81;; + exceptions in recurring events
82;; + the parser is too soft
83;; + error log is incomplete
84;; + nice to have: #include "webcal://foo.com/some-calendar.ics"
9dd9ed20 85;; + timezones, currently all times are local!
707c20a8 86
e0cd68ee 87;; * Export from diary to ical
707c20a8
GM
88;; + diary-date, diary-float, and self-made sexp entries are not
89;; understood
707c20a8
GM
90
91;; * Other things
707c20a8
GM
92;; + clean up all those date/time parsing functions
93;; + Handle todo items?
94;; + Check iso 8601 for datetime and period
95;; + Which chars to (un)escape?
707c20a8
GM
96
97
98;;; Code:
99
9dd9ed20 100(defconst icalendar-version 0.12
707c20a8
GM
101 "Version number of icalendar.el.")
102
103;; ======================================================================
104;; Customizables
105;; ======================================================================
106(defgroup icalendar nil
107 "Icalendar support."
108 :prefix "icalendar-"
109 :group 'calendar)
110
111(defcustom icalendar-import-format
112 "%s%d%l%o"
113 "Format string for importing events from iCalendar into Emacs diary.
114This string defines how iCalendar events are inserted into diary
115file. Meaning of the specifiers:
116%d Description, see `icalendar-import-format-description'
117%l Location, see `icalendar-import-format-location'
118%o Organizer, see `icalendar-import-format-organizer'
119%s Subject, see `icalendar-import-format-subject'"
120 :type 'string
121 :group 'icalendar)
122
123(defcustom icalendar-import-format-subject
124 "%s"
125 "Format string defining how the subject element is formatted.
126This applies only if the subject is not empty! `%s' is replaced
127by the subject."
128 :type 'string
129 :group 'icalendar)
130
131(defcustom icalendar-import-format-description
132 "\n Desc: %s"
133 "Format string defining how the description element is formatted.
134This applies only if the description is not empty! `%s' is
135replaced by the description."
136 :type 'string
137 :group 'icalendar)
138
139(defcustom icalendar-import-format-location
140 "\n Location: %s"
141 "Format string defining how the location element is formatted.
142This applies only if the location is not empty! `%s' is replaced
143by the location."
144 :type 'string
145 :group 'icalendar)
146
147(defcustom icalendar-import-format-organizer
148 "\n Organizer: %s"
149 "Format string defining how the organizer element is formatted.
150This applies only if the organizer is not empty! `%s' is
151replaced by the organizer."
152 :type 'string
153 :group 'icalendar)
154
9dd9ed20
GM
155(defvar icalendar-debug nil
156 "Enable icalendar debug messages.")
707c20a8
GM
157
158;; ======================================================================
159;; NO USER SERVICABLE PARTS BELOW THIS LINE
160;; ======================================================================
161
f2aa5449 162(defconst icalendar--weekday-array ["SU" "MO" "TU" "WE" "TH" "FR" "SA"])
707c20a8 163
707c20a8
GM
164;; ======================================================================
165;; all the other libs we need
166;; ======================================================================
167(require 'calendar)
707c20a8 168
e0cd68ee
GM
169;; ======================================================================
170;; misc
171;; ======================================================================
172(defun icalendar--dmsg (&rest args)
173 "Print message ARGS if `icalendar-debug' is non-nil."
174 (if icalendar-debug
175 (apply 'message args)))
176
707c20a8
GM
177;; ======================================================================
178;; Core functionality
179;; Functions for parsing icalendars, importing and so on
180;; ======================================================================
181
e0cd68ee 182(defun icalendar--get-unfolded-buffer (folded-ical-buffer)
707c20a8
GM
183 "Return a new buffer containing the unfolded contents of a buffer.
184Folding is the iCalendar way of wrapping long lines. In the
185created buffer all occurrences of CR LF BLANK are replaced by the
186empty string. Argument FOLDED-ICAL-BUFFER is the unfolded input
187buffer."
188 (let ((unfolded-buffer (get-buffer-create " *icalendar-work*")))
189 (save-current-buffer
190 (set-buffer unfolded-buffer)
191 (erase-buffer)
192 (insert-buffer folded-ical-buffer)
193 (while (re-search-forward "\r?\n[ \t]" nil t)
e0cd68ee 194 (replace-match "" nil nil)))
707c20a8
GM
195 unfolded-buffer))
196
e0cd68ee
GM
197(defsubst icalendar--rris (re rp st)
198 "Replace regexp RE with RP in string ST and return the new string.
199This is here for compatibility with XEmacs."
707c20a8
GM
200 ;; XEmacs:
201 (if (fboundp 'replace-in-string)
202 (save-match-data ;; apparently XEmacs needs save-match-data
203 (replace-in-string st re rp))
204 ;; Emacs:
205 (replace-regexp-in-string re rp st)))
206
e0cd68ee 207(defun icalendar--read-element (invalue inparams)
707c20a8
GM
208 "Recursively read the next iCalendar element in the current buffer.
209INVALUE gives the current iCalendar element we are reading.
210INPARAMS gives the current parameters.....
211This function calls itself recursively for each nested calendar element
212it finds"
213 (let (element children line name params param param-name param-value
214 value
e0cd68ee 215 (continue t))
707c20a8
GM
216 (setq children '())
217 (while (and continue
218 (re-search-forward "^\\([A-Za-z0-9-]+\\)[;:]" nil t))
219 (setq name (intern (match-string 1)))
220 (backward-char 1)
221 (setq params '())
222 (setq line '())
223 (while (looking-at ";")
224 (re-search-forward ";\\([A-Za-z0-9-]+\\)=" nil nil)
225 (setq param-name (intern (match-string 1)))
226 (re-search-forward "\\(\\([^;,:\"]+\\)\\|\"\\([^\"]+\\)\"\\)[;:]"
227 nil t)
228 (backward-char 1)
229 (setq param-value (or (match-string 2) (match-string 3)))
230 (setq param (list param-name param-value))
231 (while (looking-at ",")
232 (re-search-forward "\\(\\([^;,:]+\\)\\|\"\\([^\"]+\\)\"\\)"
233 nil t)
234 (if (match-string 2)
235 (setq param-value (match-string 2))
236 (setq param-value (match-string 3)))
237 (setq param (append param param-value)))
238 (setq params (append params param)))
239 (unless (looking-at ":")
240 (error "Oops"))
241 (forward-char 1)
242 (re-search-forward "\\(.*\\)\\(\r?\n[ \t].*\\)*" nil t)
e0cd68ee 243 (setq value (icalendar--rris "\r?\n[ \t]" "" (match-string 0)))
707c20a8
GM
244 (setq line (list name params value))
245 (cond ((eq name 'BEGIN)
246 (setq children
247 (append children
e0cd68ee
GM
248 (list (icalendar--read-element (intern value)
249 params)))))
707c20a8
GM
250 ((eq name 'END)
251 (setq continue nil))
252 (t
253 (setq element (append element (list line))))))
254 (if invalue
255 (list invalue inparams element children)
256 children)))
257
258;; ======================================================================
259;; helper functions for examining events
260;; ======================================================================
261
e0cd68ee
GM
262;;(defsubst icalendar--get-all-event-properties (event)
263;; "Return the list of properties in this EVENT."
264;; (car (cddr event)))
707c20a8 265
e0cd68ee 266(defun icalendar--get-event-property (event prop)
74692b14 267 "For the given EVENT return the value of the first occurence of PROP."
707c20a8
GM
268 (catch 'found
269 (let ((props (car (cddr event))) pp)
270 (while props
271 (setq pp (car props))
272 (if (eq (car pp) prop)
273 (throw 'found (car (cddr pp))))
274 (setq props (cdr props))))
275 nil))
276
74692b14
GM
277(defun icalendar--get-event-property-attributes (event prop)
278 "For the given EVENT return attributes of the first occurence of PROP."
279 (catch 'found
280 (let ((props (car (cddr event))) pp)
281 (while props
282 (setq pp (car props))
283 (if (eq (car pp) prop)
284 (throw 'found (cadr pp)))
285 (setq props (cdr props))))
286 nil))
287
288(defun icalendar--get-event-properties (event prop)
289 "For the given EVENT return a list of all values of the property PROP."
290 (let ((props (car (cddr event))) pp result)
291 (while props
292 (setq pp (car props))
293 (if (eq (car pp) prop)
9dd9ed20 294 (setq result (append (split-string (car (cddr pp)) ",") result)))
74692b14
GM
295 (setq props (cdr props)))
296 result))
297
e0cd68ee
GM
298;; (defun icalendar--set-event-property (event prop new-value)
299;; "For the given EVENT set the property PROP to the value NEW-VALUE."
300;; (catch 'found
301;; (let ((props (car (cddr event))) pp)
302;; (while props
303;; (setq pp (car props))
304;; (when (eq (car pp) prop)
305;; (setcdr (cdr pp) new-value)
306;; (throw 'found (car (cddr pp))))
307;; (setq props (cdr props)))
308;; (setq props (car (cddr event)))
309;; (setcar (cddr event)
310;; (append props (list (list prop nil new-value)))))))
311
312(defun icalendar--get-children (node name)
707c20a8
GM
313 "Return all children of the given NODE which have a name NAME.
314For instance the VCALENDAR node can have VEVENT children as well as VTODO
315children."
316 (let ((result nil)
317 (children (cadr (cddr node))))
318 (when (eq (car node) name)
319 (setq result node))
320 ;;(message "%s" node)
321 (when children
322 (let ((subresult
323 (delq nil
e0cd68ee
GM
324 (mapcar (lambda (n)
325 (icalendar--get-children n name))
326 children))))
707c20a8
GM
327 (if subresult
328 (if result
329 (setq result (append result subresult))
330 (setq result subresult)))))
331 result))
332
e0cd68ee
GM
333 ; private
334(defun icalendar--all-events (icalendar)
707c20a8 335 "Return the list of all existing events in the given ICALENDAR."
e0cd68ee 336 (icalendar--get-children (car icalendar) 'VEVENT))
707c20a8 337
e0cd68ee 338(defun icalendar--split-value (value-string)
74692b14 339 "Split VALUE-STRING at ';='."
707c20a8
GM
340 (let ((result '())
341 param-name param-value)
342 (when value-string
343 (save-current-buffer
81d56594 344 (set-buffer (get-buffer-create " *icalendar-work*"))
707c20a8
GM
345 (set-buffer-modified-p nil)
346 (erase-buffer)
347 (insert value-string)
348 (goto-char (point-min))
349 (while
e0cd68ee
GM
350 (re-search-forward
351 "\\([A-Za-z0-9-]+\\)=\\(\\([^;,:]+\\)\\|\"\\([^\"]+\\)\"\\);?"
352 nil t)
707c20a8
GM
353 (setq param-name (intern (match-string 1)))
354 (setq param-value (match-string 2))
355 (setq result
e0cd68ee 356 (append result (list (list param-name param-value)))))))
707c20a8
GM
357 result))
358
8ee7eb6b 359(defun icalendar--decode-isodatetime (isodatetimestring &optional day-shift)
707c20a8 360 "Return ISODATETIMESTRING in format like `decode-time'.
8ee7eb6b
GM
361Converts from ISO-8601 to Emacs representation. If
362ISODATETIMESTRING specifies UTC time (trailing letter Z) the
363decoded time is given in the local time zone! If optional
364parameter DAY-SHIFT is non-nil the result is shifted by DAY-SHIFT
365days.
366
367FIXME: TZID-attributes are ignored....!
368FIXME: multiple comma-separated values should be allowed!"
e0cd68ee 369 (icalendar--dmsg isodatetimestring)
707c20a8
GM
370 (if isodatetimestring
371 ;; day/month/year must be present
372 (let ((year (read (substring isodatetimestring 0 4)))
373 (month (read (substring isodatetimestring 4 6)))
374 (day (read (substring isodatetimestring 6 8)))
375 (hour 0)
376 (minute 0)
377 (second 0))
378 (when (> (length isodatetimestring) 12)
e0cd68ee 379 ;; hour/minute present
707c20a8
GM
380 (setq hour (read (substring isodatetimestring 9 11)))
381 (setq minute (read (substring isodatetimestring 11 13))))
382 (when (> (length isodatetimestring) 14)
e0cd68ee 383 ;; seconds present
707c20a8
GM
384 (setq second (read (substring isodatetimestring 13 15))))
385 (when (and (> (length isodatetimestring) 15)
e0cd68ee 386 ;; UTC specifier present
707c20a8
GM
387 (char-equal ?Z (aref isodatetimestring 15)))
388 ;; if not UTC add current-time-zone offset
389 (setq second (+ (car (current-time-zone)) second)))
8ee7eb6b
GM
390 ;; shift if necessary
391 (if day-shift
392 (let ((mdy (calendar-gregorian-from-absolute
393 (+ (calendar-absolute-from-gregorian
394 (list month day year))
395 day-shift))))
396 (setq month (nth 0 mdy))
397 (setq day (nth 1 mdy))
398 (setq year (nth 2 mdy))))
707c20a8
GM
399 ;; create the decoded date-time
400 ;; FIXME!?!
401 (condition-case nil
402 (decode-time (encode-time second minute hour day month year))
403 (error
404 (message "Cannot decode \"%s\"" isodatetimestring)
405 ;; hope for the best...
406 (list second minute hour day month year 0 nil 0))))
407 ;; isodatetimestring == nil
408 nil))
409
9dd9ed20
GM
410(defun icalendar--decode-isoduration (isodurationstring
411 &optional duration-correction)
412 "Convert ISODURATIONSTRING into format provided by `decode-time'.
707c20a8
GM
413Converts from ISO-8601 to Emacs representation. If ISODURATIONSTRING
414specifies UTC time (trailing letter Z) the decoded time is given in
86fc29f9
GM
415the local time zone!
416
9dd9ed20
GM
417Optional argument DURATION-CORRECTION shortens result by one day.
418
86fc29f9
GM
419FIXME: TZID-attributes are ignored....!
420FIXME: multiple comma-separated values should be allowed!"
707c20a8
GM
421 (if isodurationstring
422 (save-match-data
423 (string-match
424 (concat
425 "^P[+-]?\\("
426 "\\(\\([0-9]+\\)D\\)" ; days only
427 "\\|"
428 "\\(\\(\\([0-9]+\\)D\\)?T\\(\\([0-9]+\\)H\\)?" ; opt days
e0cd68ee 429 "\\(\\([0-9]+\\)M\\)?\\(\\([0-9]+\\)S\\)?\\)" ; mand. time
707c20a8
GM
430 "\\|"
431 "\\(\\([0-9]+\\)W\\)" ; weeks only
432 "\\)$") isodurationstring)
433 (let ((seconds 0)
434 (minutes 0)
435 (hours 0)
436 (days 0)
437 (months 0)
438 (years 0))
e0cd68ee
GM
439 (cond
440 ((match-beginning 2) ;days only
441 (setq days (read (substring isodurationstring
442 (match-beginning 3)
443 (match-end 3))))
9dd9ed20 444 (when duration-correction
e0cd68ee
GM
445 (setq days (1- days))))
446 ((match-beginning 4) ;days and time
447 (if (match-beginning 5)
448 (setq days (* 7 (read (substring isodurationstring
449 (match-beginning 6)
450 (match-end 6))))))
451 (if (match-beginning 7)
452 (setq hours (read (substring isodurationstring
453 (match-beginning 8)
454 (match-end 8)))))
455 (if (match-beginning 9)
456 (setq minutes (read (substring isodurationstring
457 (match-beginning 10)
458 (match-end 10)))))
459 (if (match-beginning 11)
460 (setq seconds (read (substring isodurationstring
461 (match-beginning 12)
74692b14 462 (match-end 12))))))
e0cd68ee
GM
463 ((match-beginning 13) ;weeks only
464 (setq days (* 7 (read (substring isodurationstring
465 (match-beginning 14)
74692b14 466 (match-end 14)))))))
e0cd68ee 467 (list seconds minutes hours days months years)))
707c20a8
GM
468 ;; isodatetimestring == nil
469 nil))
470
e0cd68ee 471(defun icalendar--add-decoded-times (time1 time2)
707c20a8
GM
472 "Add TIME1 to TIME2.
473Both times must be given in decoded form. One of these times must be
474valid (year > 1900 or something)."
475 ;; FIXME: does this function exist already?
476 (decode-time (encode-time
477 (+ (nth 0 time1) (nth 0 time2))
478 (+ (nth 1 time1) (nth 1 time2))
479 (+ (nth 2 time1) (nth 2 time2))
480 (+ (nth 3 time1) (nth 3 time2))
481 (+ (nth 4 time1) (nth 4 time2))
482 (+ (nth 5 time1) (nth 5 time2))
483 nil
484 nil
485 ;;(or (nth 6 time1) (nth 6 time2)) ;; FIXME?
486 )))
487
74692b14 488(defun icalendar--datetime-to-noneuropean-date (datetime &optional separator)
707c20a8 489 "Convert the decoded DATETIME to non-european-style format.
74692b14
GM
490Optional argument SEPARATOR gives the separator between month,
491day, and year. If nil a blank character is used as separator.
492Non-European format: \"month day year\"."
707c20a8 493 (if datetime
74692b14
GM
494 (format "%d%s%d%s%d" (nth 4 datetime) ;month
495 (or separator " ")
496 (nth 3 datetime) ;day
497 (or separator " ")
498 (nth 5 datetime)) ;year
707c20a8
GM
499 ;; datetime == nil
500 nil))
501
74692b14 502(defun icalendar--datetime-to-european-date (datetime &optional separator)
707c20a8 503 "Convert the decoded DATETIME to European format.
74692b14
GM
504Optional argument SEPARATOR gives the separator between month,
505day, and year. If nil a blank character is used as separator.
707c20a8
GM
506European format: (day month year).
507FIXME"
508 (if datetime
74692b14
GM
509 (format "%d%s%d%s%d" (nth 3 datetime) ;day
510 (or separator " ")
e0cd68ee 511 (nth 4 datetime) ;month
74692b14 512 (or separator " ")
e0cd68ee 513 (nth 5 datetime)) ;year
707c20a8
GM
514 ;; datetime == nil
515 nil))
516
74692b14
GM
517(defun icalendar--datetime-to-diary-date (datetime &optional separator)
518 "Convert the decoded DATETIME to diary format.
519Optional argument SEPARATOR gives the separator between month,
520day, and year. If nil a blank character is used as separator.
521Call icalendar--datetime-to-(non)-european-date according to
522value of `european-calendar-style'."
523 (if european-calendar-style
524 (icalendar--datetime-to-european-date datetime separator)
525 (icalendar--datetime-to-noneuropean-date datetime separator)))
526
e0cd68ee 527(defun icalendar--datetime-to-colontime (datetime)
707c20a8
GM
528 "Extract the time part of a decoded DATETIME into 24-hour format.
529Note that this silently ignores seconds."
530 (format "%02d:%02d" (nth 2 datetime) (nth 1 datetime)))
531
e0cd68ee 532(defun icalendar--get-month-number (monthname)
707c20a8 533 "Return the month number for the given MONTHNAME."
f2aa5449
GM
534 (catch 'found
535 (let ((num 1)
536 (m (downcase monthname)))
537 (mapc (lambda (month)
538 (let ((mm (downcase month)))
539 (if (or (string-equal mm m)
540 (string-equal (substring mm 0 3) m))
541 (throw 'found num))
542 (setq num (1+ num))))
543 calendar-month-name-array))
544 ;; Error:
545 -1))
546
547(defun icalendar--get-weekday-number (abbrevweekday)
548 "Return the number for the ABBREVWEEKDAY."
74692b14
GM
549 (if abbrevweekday
550 (catch 'found
551 (let ((num 0)
552 (aw (downcase abbrevweekday)))
553 (mapc (lambda (day)
554 (let ((d (downcase day)))
555 (if (string-equal d aw)
556 (throw 'found num))
557 (setq num (1+ num))))
558 icalendar--weekday-array)))
f2aa5449
GM
559 ;; Error:
560 -1))
707c20a8 561
e0cd68ee 562(defun icalendar--get-weekday-abbrev (weekday)
707c20a8 563 "Return the abbreviated WEEKDAY."
f2aa5449
GM
564 (catch 'found
565 (let ((num 0)
566 (w (downcase weekday)))
567 (mapc (lambda (day)
568 (let ((d (downcase day)))
569 (if (or (string-equal d w)
570 (string-equal (substring d 0 3) w))
571 (throw 'found (aref icalendar--weekday-array num)))
572 (setq num (1+ num))))
573 calendar-day-name-array))
574 ;; Error:
81d56594
GM
575 nil))
576
577(defun icalendar--date-to-isodate (date &optional day-shift)
578 "Convert DATE to iso-style date.
579DATE must be a list of the form (month day year).
580If DAY-SHIFT is non-nil, the result is shifted by DAY-SHIFT days."
581 (let ((mdy (calendar-gregorian-from-absolute
582 (+ (calendar-absolute-from-gregorian date)
583 (or day-shift 0)))))
584 (format "%04d%02d%02d" (nth 2 mdy) (nth 0 mdy) (nth 1 mdy))))
585
707c20a8 586
e0cd68ee 587(defun icalendar--datestring-to-isodate (datestring &optional day-shift)
707c20a8
GM
588 "Convert diary-style DATESTRING to iso-style date.
589If DAY-SHIFT is non-nil, the result is shifted by DAY-SHIFT days
590-- DAY-SHIFT must be either nil or an integer. This function
591takes care of european-style."
592 (let ((day -1) month year)
593 (save-match-data
e0cd68ee
GM
594 (cond ( ;; numeric date
595 (string-match (concat "\\s-*"
596 "0?\\([1-9][0-9]?\\)[ \t/]\\s-*"
597 "0?\\([1-9][0-9]?\\),?[ \t/]\\s-*"
598 "\\([0-9]\\{4\\}\\)")
599 datestring)
600 (setq day (read (substring datestring (match-beginning 1)
601 (match-end 1))))
602 (setq month (read (substring datestring (match-beginning 2)
603 (match-end 2))))
604 (setq year (read (substring datestring (match-beginning 3)
605 (match-end 3))))
606 (unless european-calendar-style
607 (let ((x month))
608 (setq month day)
609 (setq day x))))
610 ( ;; date contains month names -- european-style
611 (and european-calendar-style
612 (string-match (concat "\\s-*"
613 "0?\\([123]?[0-9]\\)[ \t/]\\s-*"
614 "\\([A-Za-z][^ ]+\\)[ \t/]\\s-*"
615 "\\([0-9]\\{4\\}\\)")
616 datestring))
617 (setq day (read (substring datestring (match-beginning 1)
618 (match-end 1))))
619 (setq month (icalendar--get-month-number
620 (substring datestring (match-beginning 2)
621 (match-end 2))))
622 (setq year (read (substring datestring (match-beginning 3)
623 (match-end 3)))))
624 ( ;; date contains month names -- non-european-style
625 (and (not european-calendar-style)
626 (string-match (concat "\\s-*"
627 "\\([A-Za-z][^ ]+\\)[ \t/]\\s-*"
628 "0?\\([123]?[0-9]\\),?[ \t/]\\s-*"
629 "\\([0-9]\\{4\\}\\)")
630 datestring))
631 (setq day (read (substring datestring (match-beginning 2)
632 (match-end 2))))
633 (setq month (icalendar--get-month-number
634 (substring datestring (match-beginning 1)
635 (match-end 1))))
636 (setq year (read (substring datestring (match-beginning 3)
637 (match-end 3)))))
638 (t
639 nil)))
707c20a8 640 (if (> day 0)
e0cd68ee
GM
641 (let ((mdy (calendar-gregorian-from-absolute
642 (+ (calendar-absolute-from-gregorian (list month day
81d56594 643 year))
e0cd68ee
GM
644 (or day-shift 0)))))
645 (format "%04d%02d%02d" (nth 2 mdy) (nth 0 mdy) (nth 1 mdy)))
707c20a8
GM
646 nil)))
647
e0cd68ee 648(defun icalendar--diarytime-to-isotime (timestring ampmstring)
707c20a8
GM
649 "Convert a a time like 9:30pm to an iso-conform string like T213000.
650In this example the TIMESTRING would be \"9:30\" and the AMPMSTRING
651would be \"pm\"."
652 (if timestring
e0cd68ee 653 (let ((starttimenum (read (icalendar--rris ":" "" timestring))))
707c20a8
GM
654 ;; take care of am/pm style
655 (if (and ampmstring (string= "pm" ampmstring))
656 (setq starttimenum (+ starttimenum 1200)))
657 (format "T%04d00" starttimenum))
658 nil))
659
74692b14
GM
660(defun icalendar--convert-string-for-export (string)
661 "Escape comma and other critical characters in STRING."
662 (icalendar--rris "," "\\\\," string))
707c20a8 663
e0cd68ee 664(defun icalendar--convert-string-for-import (string)
707c20a8 665 "Remove escape chars for comma, semicolon etc. from STRING."
e0cd68ee
GM
666 (icalendar--rris
667 "\\\\n" "\n " (icalendar--rris
668 "\\\\\"" "\"" (icalendar--rris
669 "\\\\;" ";" (icalendar--rris
670 "\\\\," "," string)))))
707c20a8
GM
671
672;; ======================================================================
e0cd68ee 673;; Export -- convert emacs-diary to icalendar
707c20a8
GM
674;; ======================================================================
675
86fc29f9 676;;;###autoload
e0cd68ee
GM
677(defun icalendar-export-file (diary-filename ical-filename)
678 "Export diary file to iCalendar format.
679All diary entries in the file DIARY-FILENAME are converted to iCalendar
680format. The result is appended to the file ICAL-FILENAME."
81d56594 681 (interactive "FExport diary data from file:
707c20a8 682Finto iCalendar file: ")
e0cd68ee
GM
683 (save-current-buffer
684 (set-buffer (find-file diary-filename))
685 (icalendar-export-region (point-min) (point-max) ical-filename)))
686
687(defalias 'icalendar-convert-diary-to-ical 'icalendar-export-file)
81d56594 688(make-obsolete 'icalendar-convert-diary-to-ical 'icalendar-export-file)
e0cd68ee 689
86fc29f9 690;;;###autoload
e0cd68ee
GM
691(defun icalendar-export-region (min max ical-filename)
692 "Export region in diary file to iCalendar format.
693All diary entries in the region from MIN to MAX in the current buffer are
694converted to iCalendar format. The result is appended to the file
81d56594 695ICAL-FILENAME.
74692b14
GM
696This function attempts to return t if something goes wrong. In this
697case an error string which describes all the errors and problems is
698written into the buffer `*icalendar-errors*'."
e0cd68ee
GM
699 (interactive "r
700FExport diary data into iCalendar file: ")
707c20a8
GM
701 (let ((result "")
702 (start 0)
703 (entry-main "")
704 (entry-rest "")
705 (header "")
706 (contents)
81d56594 707 (found-error nil)
707c20a8 708 (nonmarker (concat "^" (regexp-quote diary-nonmarking-symbol)
e0cd68ee 709 "?")))
81d56594
GM
710 ;; prepare buffer with error messages
711 (save-current-buffer
9dd9ed20 712 (set-buffer (get-buffer-create "*icalendar-errors*"))
81d56594 713 (erase-buffer))
74692b14 714
81d56594 715 ;; here we go
e0cd68ee
GM
716 (save-excursion
717 (goto-char min)
707c20a8 718 (while (re-search-forward
9dd9ed20 719 "^\\([^ \t\n].+\\)\\(\\(\n[ \t].*\\)*\\)" max t)
707c20a8
GM
720 (setq entry-main (match-string 1))
721 (if (match-beginning 2)
722 (setq entry-rest (match-string 2))
723 (setq entry-rest ""))
724 (setq header (format "\nBEGIN:VEVENT\nUID:emacs%d%d%d"
725 (car (current-time))
726 (cadr (current-time))
727 (car (cddr (current-time)))))
81d56594
GM
728 (condition-case error-val
729 (progn
9dd9ed20
GM
730 (setq contents
731 (or
732 ;; anniversaries -- %%(diary-anniversary ...)
733 (icalendar--convert-anniversary-to-ical nonmarker
734 entry-main)
735 ;; cyclic events -- %%(diary-cyclic ...)
736 (icalendar--convert-cyclic-to-ical nonmarker entry-main)
737 ;; diary-date -- %%(diary-date ...)
738 (icalendar--convert-date-to-ical nonmarker entry-main)
739 ;; float events -- %%(diary-float ...)
740 (icalendar--convert-float-to-ical nonmarker entry-main)
741 ;; block events -- %%(diary-block ...)
742 (icalendar--convert-block-to-ical nonmarker entry-main)
743 ;; other sexp diary entries
744 (icalendar--convert-sexp-to-ical nonmarker entry-main)
745 ;; weekly by day -- Monday 8:30 Team meeting
746 (icalendar--convert-weekly-to-ical nonmarker entry-main)
747 ;; yearly by day -- 1 May Tag der Arbeit
748 (icalendar--convert-yearly-to-ical nonmarker entry-main)
749 ;; "ordinary" events, start and end time given
750 ;; 1 Feb 2003 blah
751 (icalendar--convert-ordinary-to-ical nonmarker entry-main)
752 ;; everything else
753 ;; Oops! what's that?
754 (error "Could not parse entry")))
755 (unless (string= entry-rest "")
756 (setq contents
757 (concat contents "\nDESCRIPTION:"
758 (icalendar--convert-string-for-export
759 entry-rest))))
81d56594
GM
760 (setq result (concat result header contents "\nEND:VEVENT")))
761 ;; handle errors
762 (error
763 (setq found-error t)
764 (save-current-buffer
9dd9ed20 765 (set-buffer (get-buffer-create "*icalendar-errors*"))
81d56594
GM
766 (insert (format "Error in line %d -- %s: `%s'\n"
767 (count-lines (point-min) (point))
768 (cadr error-val)
769 entry-main))))))
770
707c20a8 771 ;; we're done, insert everything into the file
74692b14 772 (save-current-buffer
8ee7eb6b 773 (let ((coding-system-for-write 'utf-8))
74692b14
GM
774 (set-buffer (find-file ical-filename))
775 (goto-char (point-max))
776 (insert "BEGIN:VCALENDAR")
777 (insert "\nPRODID:-//Emacs//NONSGML icalendar.el//EN")
778 (insert "\nVERSION:2.0")
779 (insert result)
780 (insert "\nEND:VCALENDAR\n")
781 ;; save the diary file
782 (save-buffer))))
81d56594 783 found-error))
707c20a8 784
9dd9ed20
GM
785;; subroutines
786(defun icalendar--convert-ordinary-to-ical (nonmarker entry-main)
787 "Convert \"ordinary\" diary entry to icalendar format.
788
789NONMARKER is a regular expression matching the start of non-marking
790entries. ENTRY-MAIN is the first line of the diary entry."
791 (if (string-match (concat nonmarker
792 "\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\)\\s-*"
793 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
794 "\\("
795 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
796 "\\)?"
797 "\\s-*\\(.*\\)")
798 entry-main)
799 (let* ((datetime (substring entry-main (match-beginning 1)
800 (match-end 1)))
801 (startisostring (icalendar--datestring-to-isodate
802 datetime))
803 (endisostring (icalendar--datestring-to-isodate
804 datetime 1))
805 (starttimestring (icalendar--diarytime-to-isotime
806 (if (match-beginning 3)
807 (substring entry-main
808 (match-beginning 3)
809 (match-end 3))
810 nil)
811 (if (match-beginning 4)
812 (substring entry-main
813 (match-beginning 4)
814 (match-end 4))
815 nil)))
816 (endtimestring (icalendar--diarytime-to-isotime
817 (if (match-beginning 6)
818 (substring entry-main
819 (match-beginning 6)
820 (match-end 6))
821 nil)
822 (if (match-beginning 7)
823 (substring entry-main
824 (match-beginning 7)
825 (match-end 7))
826 nil)))
827 (summary (icalendar--convert-string-for-export
828 (substring entry-main (match-beginning 8)
829 (match-end 8)))))
830 (icalendar--dmsg "ordinary %s" entry-main)
831
832 (unless startisostring
833 (error "Could not parse date"))
834 (when starttimestring
835 (unless endtimestring
836 (let ((time
837 (read (icalendar--rris "^T0?" ""
838 starttimestring))))
839 (setq endtimestring (format "T%06d"
840 (+ 10000 time))))))
841 (concat "\nDTSTART;"
842 (if starttimestring "VALUE=DATE-TIME:"
843 "VALUE=DATE:")
844 startisostring
845 (or starttimestring "")
846 "\nDTEND;"
847 (if endtimestring "VALUE=DATE-TIME:"
848 "VALUE=DATE:")
849 (if starttimestring
850 startisostring
851 endisostring)
852 (or endtimestring "")
853 "\nSUMMARY:"
854 summary))
855 ;; no match
856 nil))
857
858(defun icalendar--convert-weekly-to-ical (nonmarker entry-main)
859 "Convert weekly diary entry to icalendar format.
860
861NONMARKER is a regular expression matching the start of non-marking
862entries. ENTRY-MAIN is the first line of the diary entry."
863 (if (and (string-match (concat nonmarker
864 "\\([a-z]+\\)\\s-+"
865 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)"
866 "\\([ap]m\\)?"
867 "\\(-0?"
868 "\\([1-9][0-9]?:[0-9][0-9]\\)"
869 "\\([ap]m\\)?\\)?"
870 "\\)?"
871 "\\s-*\\(.*\\)$")
872 entry-main)
873 (icalendar--get-weekday-abbrev
874 (substring entry-main (match-beginning 1)
875 (match-end 1))))
876 (let* ((day (icalendar--get-weekday-abbrev
877 (substring entry-main (match-beginning 1)
878 (match-end 1))))
879 (starttimestring (icalendar--diarytime-to-isotime
880 (if (match-beginning 3)
881 (substring entry-main
882 (match-beginning 3)
883 (match-end 3))
884 nil)
885 (if (match-beginning 4)
886 (substring entry-main
887 (match-beginning 4)
888 (match-end 4))
889 nil)))
890 (endtimestring (icalendar--diarytime-to-isotime
891 (if (match-beginning 6)
892 (substring entry-main
893 (match-beginning 6)
894 (match-end 6))
895 nil)
896 (if (match-beginning 7)
897 (substring entry-main
898 (match-beginning 7)
899 (match-end 7))
900 nil)))
901 (summary (icalendar--convert-string-for-export
902 (substring entry-main (match-beginning 8)
903 (match-end 8)))))
904 (icalendar--dmsg "weekly %s" entry-main)
905
906 (when starttimestring
907 (unless endtimestring
908 (let ((time (read
909 (icalendar--rris "^T0?" ""
910 starttimestring))))
911 (setq endtimestring (format "T%06d"
912 (+ 10000 time))))))
913 (concat "\nDTSTART;"
914 (if starttimestring
915 "VALUE=DATE-TIME:"
916 "VALUE=DATE:")
917 ;; find the correct week day,
918 ;; 1st january 2000 was a saturday
919 (format
920 "200001%02d"
921 (+ (icalendar--get-weekday-number day) 2))
922 (or starttimestring "")
923 "\nDTEND;"
924 (if endtimestring
925 "VALUE=DATE-TIME:"
926 "VALUE=DATE:")
927 (format
928 "200001%02d"
929 ;; end is non-inclusive!
930 (+ (icalendar--get-weekday-number day)
931 (if endtimestring 2 3)))
932 (or endtimestring "")
933 "\nSUMMARY:" summary
934 "\nRRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY="
935 day))
936 ;; no match
937 nil))
938
939(defun icalendar--convert-yearly-to-ical (nonmarker entry-main)
940 "Convert yearly diary entry to icalendar format.
941
942NONMARKER is a regular expression matching the start of non-marking
943entries. ENTRY-MAIN is the first line of the diary entry."
944 (if (string-match (concat nonmarker
945 (if european-calendar-style
946 "0?\\([1-9]+[0-9]?\\)\\s-+\\([a-z]+\\)\\s-+"
947 "\\([a-z]+\\)\\s-+0?\\([1-9]+[0-9]?\\)\\s-+")
948 "\\*?\\s-*"
949 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
950 "\\("
951 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
952 "\\)?"
953 "\\s-*\\([^0-9]+.*\\)$" ; must not match years
954 )
955 entry-main)
956 (let* ((daypos (if european-calendar-style 1 2))
957 (monpos (if european-calendar-style 2 1))
958 (day (read (substring entry-main
959 (match-beginning daypos)
960 (match-end daypos))))
961 (month (icalendar--get-month-number
962 (substring entry-main
963 (match-beginning monpos)
964 (match-end monpos))))
965 (starttimestring (icalendar--diarytime-to-isotime
966 (if (match-beginning 4)
967 (substring entry-main
968 (match-beginning 4)
969 (match-end 4))
970 nil)
971 (if (match-beginning 5)
972 (substring entry-main
973 (match-beginning 5)
974 (match-end 5))
975 nil)))
976 (endtimestring (icalendar--diarytime-to-isotime
977 (if (match-beginning 7)
978 (substring entry-main
979 (match-beginning 7)
980 (match-end 7))
981 nil)
982 (if (match-beginning 8)
983 (substring entry-main
984 (match-beginning 8)
985 (match-end 8))
986 nil)))
987 (summary (icalendar--convert-string-for-export
988 (substring entry-main (match-beginning 9)
989 (match-end 9)))))
990 (icalendar--dmsg "yearly %s" entry-main)
991
992 (when starttimestring
993 (unless endtimestring
994 (let ((time (read
995 (icalendar--rris "^T0?" ""
996 starttimestring))))
997 (setq endtimestring (format "T%06d"
998 (+ 10000 time))))))
999 (concat "\nDTSTART;"
1000 (if starttimestring "VALUE=DATE-TIME:"
1001 "VALUE=DATE:")
1002 (format "1900%02d%02d" month day)
1003 (or starttimestring "")
1004 "\nDTEND;"
1005 (if endtimestring "VALUE=DATE-TIME:"
1006 "VALUE=DATE:")
1007 ;; end is not included! shift by one day
1008 (icalendar--date-to-isodate
1009 (list month day 1900)
1010 (if endtimestring 0 1))
1011 (or endtimestring "")
1012 "\nSUMMARY:"
1013 summary
1014 "\nRRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH="
1015 (format "%2d" month)
1016 ";BYMONTHDAY="
1017 (format "%2d" day)))
1018 ;; no match
1019 nil))
1020
1021(defun icalendar--convert-sexp-to-ical (nonmarker entry-main)
1022 "Convert complex sexp diary entry to icalendar format -- unsupported!
1023
1024FIXME!
1025
1026NONMARKER is a regular expression matching the start of non-marking
1027entries. ENTRY-MAIN is the first line of the diary entry."
1028 (if (string-match (concat nonmarker
1029 "%%(\\([^)]+\\))\\s-*\\(.*\\)")
1030 entry-main)
1031 (progn
1032 (icalendar--dmsg "diary-sexp %s" entry-main)
1033 (error "Sexp-entries are not supported yet"))
1034 ;; no match
1035 nil))
1036
1037(defun icalendar--convert-block-to-ical (nonmarker entry-main)
1038 "Convert block diary entry to icalendar format.
1039
1040NONMARKER is a regular expression matching the start of non-marking
1041entries. ENTRY-MAIN is the first line of the diary entry."
1042 (if (string-match (concat nonmarker
1043 "%%(diary-block \\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\)"
1044 " +\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\))\\s-*"
1045 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
1046 "\\("
1047 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1048 "\\)?"
1049 "\\s-*\\(.*\\)")
1050 entry-main)
1051 (let* ((startstring (substring entry-main
1052 (match-beginning 1)
1053 (match-end 1)))
1054 (endstring (substring entry-main
1055 (match-beginning 2)
1056 (match-end 2)))
1057 (startisostring (icalendar--datestring-to-isodate
1058 startstring))
1059 (endisostring (icalendar--datestring-to-isodate
1060 endstring))
1061 (endisostring+1 (icalendar--datestring-to-isodate
1062 endstring 1))
1063 (starttimestring (icalendar--diarytime-to-isotime
1064 (if (match-beginning 4)
1065 (substring entry-main
1066 (match-beginning 4)
1067 (match-end 4))
1068 nil)
1069 (if (match-beginning 5)
1070 (substring entry-main
1071 (match-beginning 5)
1072 (match-end 5))
1073 nil)))
1074 (endtimestring (icalendar--diarytime-to-isotime
1075 (if (match-beginning 7)
1076 (substring entry-main
1077 (match-beginning 7)
1078 (match-end 7))
1079 nil)
1080 (if (match-beginning 8)
1081 (substring entry-main
1082 (match-beginning 8)
1083 (match-end 8))
1084 nil)))
1085 (summary (icalendar--convert-string-for-export
1086 (substring entry-main (match-beginning 9)
1087 (match-end 9)))))
1088 (icalendar--dmsg "diary-block %s" entry-main)
1089 (when starttimestring
1090 (unless endtimestring
1091 (let ((time
1092 (read (icalendar--rris "^T0?" ""
1093 starttimestring))))
1094 (setq endtimestring (format "T%06d"
1095 (+ 10000 time))))))
1096 (if starttimestring
1097 ;; with time -> write rrule
1098 (concat "\nDTSTART;VALUE=DATE-TIME:"
1099 startisostring
1100 starttimestring
1101 "\nDTEND;VALUE=DATE-TIME:"
1102 startisostring
1103 endtimestring
1104 "\nSUMMARY:"
1105 summary
1106 "\nRRULE:FREQ=DAILY;INTERVAL=1;UNTIL="
1107 endisostring)
1108 ;; no time -> write long event
1109 (concat "\nDTSTART;VALUE=DATE:" startisostring
1110 "\nDTEND;VALUE=DATE:" endisostring+1
1111 "\nSUMMARY:" summary)))
1112 ;; no match
1113 nil))
1114
1115(defun icalendar--convert-float-to-ical (nonmarker entry-main)
1116 "Convert float diary entry to icalendar format -- unsupported!
1117
1118FIXME!
1119
1120NONMARKER is a regular expression matching the start of non-marking
1121entries. ENTRY-MAIN is the first line of the diary entry."
1122 (if (string-match (concat nonmarker
1123 "%%(diary-float \\([^)]+\\))\\s-*\\(.*\\)")
1124 entry-main)
1125 (progn
1126 (icalendar--dmsg "diary-float %s" entry-main)
1127 (error "`diary-float' is not supported yet"))
1128 ;; no match
1129 nil))
1130
1131(defun icalendar--convert-date-to-ical (nonmarker entry-main)
1132 "Convert `diary-date' diary entry to icalendar format -- unsupported!
1133
1134FIXME!
1135
1136NONMARKER is a regular expression matching the start of non-marking
1137entries. ENTRY-MAIN is the first line of the diary entry."
1138 (if (string-match (concat nonmarker
1139 "%%(diary-date \\([^)]+\\))\\s-*\\(.*\\)")
1140 entry-main)
1141 (progn
1142 (icalendar--dmsg "diary-date %s" entry-main)
1143 (error "`diary-date' is not supported yet"))
1144 ;; no match
1145 nil))
1146
1147(defun icalendar--convert-cyclic-to-ical (nonmarker entry-main)
1148 "Convert `diary-cyclic' diary entry to icalendar format.
1149
1150NONMARKER is a regular expression matching the start of non-marking
1151entries. ENTRY-MAIN is the first line of the diary entry."
1152 (if (string-match (concat nonmarker
1153 "%%(diary-cyclic \\([^ ]+\\) +"
1154 "\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\))\\s-*"
1155 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
1156 "\\("
1157 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1158 "\\)?"
1159 "\\s-*\\(.*\\)")
1160 entry-main)
1161 (let* ((frequency (substring entry-main (match-beginning 1)
1162 (match-end 1)))
1163 (datetime (substring entry-main (match-beginning 2)
1164 (match-end 2)))
1165 (startisostring (icalendar--datestring-to-isodate
1166 datetime))
1167 (endisostring (icalendar--datestring-to-isodate
1168 datetime))
1169 (endisostring+1 (icalendar--datestring-to-isodate
1170 datetime 1))
1171 (starttimestring (icalendar--diarytime-to-isotime
1172 (if (match-beginning 4)
1173 (substring entry-main
1174 (match-beginning 4)
1175 (match-end 4))
1176 nil)
1177 (if (match-beginning 5)
1178 (substring entry-main
1179 (match-beginning 5)
1180 (match-end 5))
1181 nil)))
1182 (endtimestring (icalendar--diarytime-to-isotime
1183 (if (match-beginning 7)
1184 (substring entry-main
1185 (match-beginning 7)
1186 (match-end 7))
1187 nil)
1188 (if (match-beginning 8)
1189 (substring entry-main
1190 (match-beginning 8)
1191 (match-end 8))
1192 nil)))
1193 (summary (icalendar--convert-string-for-export
1194 (substring entry-main (match-beginning 9)
1195 (match-end 9)))))
1196 (icalendar--dmsg "diary-cyclic %s" entry-main)
1197 (when starttimestring
1198 (unless endtimestring
1199 (let ((time
1200 (read (icalendar--rris "^T0?" ""
1201 starttimestring))))
1202 (setq endtimestring (format "T%06d"
1203 (+ 10000 time))))))
1204 (concat "\nDTSTART;"
1205 (if starttimestring "VALUE=DATE-TIME:"
1206 "VALUE=DATE:")
1207 startisostring
1208 (or starttimestring "")
1209 "\nDTEND;"
1210 (if endtimestring "VALUE=DATE-TIME:"
1211 "VALUE=DATE:")
1212 (if endtimestring endisostring endisostring+1)
1213 (or endtimestring "")
1214 "\nSUMMARY:" summary
1215 "\nRRULE:FREQ=DAILY;INTERVAL=" frequency
1216 ;; strange: korganizer does not expect
1217 ;; BYSOMETHING here...
1218 ))
1219 ;; no match
1220 nil))
1221
1222(defun icalendar--convert-anniversary-to-ical (nonmarker entry-main)
1223 "Convert `diary-anniversary' diary entry to icalendar format.
1224
1225NONMARKER is a regular expression matching the start of non-marking
1226entries. ENTRY-MAIN is the first line of the diary entry."
1227 (if (string-match (concat nonmarker
1228 "%%(diary-anniversary \\([^)]+\\))\\s-*"
1229 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
1230 "\\("
1231 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1232 "\\)?"
1233 "\\s-*\\(.*\\)")
1234 entry-main)
1235 (let* ((datetime (substring entry-main (match-beginning 1)
1236 (match-end 1)))
1237 (startisostring (icalendar--datestring-to-isodate
1238 datetime))
1239 (endisostring (icalendar--datestring-to-isodate
1240 datetime 1))
1241 (starttimestring (icalendar--diarytime-to-isotime
1242 (if (match-beginning 3)
1243 (substring entry-main
1244 (match-beginning 3)
1245 (match-end 3))
1246 nil)
1247 (if (match-beginning 4)
1248 (substring entry-main
1249 (match-beginning 4)
1250 (match-end 4))
1251 nil)))
1252 (endtimestring (icalendar--diarytime-to-isotime
1253 (if (match-beginning 6)
1254 (substring entry-main
1255 (match-beginning 6)
1256 (match-end 6))
1257 nil)
1258 (if (match-beginning 7)
1259 (substring entry-main
1260 (match-beginning 7)
1261 (match-end 7))
1262 nil)))
1263 (summary (icalendar--convert-string-for-export
1264 (substring entry-main (match-beginning 8)
1265 (match-end 8)))))
1266 (icalendar--dmsg "diary-anniversary %s" entry-main)
1267 (when starttimestring
1268 (unless endtimestring
1269 (let ((time
1270 (read (icalendar--rris "^T0?" ""
1271 starttimestring))))
1272 (setq endtimestring (format "T%06d"
1273 (+ 10000 time))))))
1274 (concat "\nDTSTART;"
1275 (if starttimestring "VALUE=DATE-TIME:"
1276 "VALUE=DATE:")
1277 startisostring
1278 (or starttimestring "")
1279 "\nDTEND;"
1280 (if endtimestring "VALUE=DATE-TIME:"
1281 "VALUE=DATE:")
1282 endisostring
1283 (or endtimestring "")
1284 "\nSUMMARY:" summary
1285 "\nRRULE:FREQ=YEARLY;INTERVAL=1"
1286 ;; the following is redundant,
1287 ;; but korganizer seems to expect this... ;(
1288 ;; and evolution doesn't understand it... :(
1289 ;; so... who is wrong?!
1290 ";BYMONTH="
1291 (substring startisostring 4 6)
1292 ";BYMONTHDAY="
1293 (substring startisostring 6 8)))
1294 ;; no match
1295 nil))
1296
707c20a8 1297;; ======================================================================
e0cd68ee 1298;; Import -- convert icalendar to emacs-diary
707c20a8
GM
1299;; ======================================================================
1300
86fc29f9 1301;;;###autoload
707c20a8 1302(defun icalendar-import-file (ical-filename diary-filename
e0cd68ee
GM
1303 &optional non-marking)
1304 "Import a iCalendar file and append to a diary file.
707c20a8
GM
1305Argument ICAL-FILENAME output iCalendar file.
1306Argument DIARY-FILENAME input `diary-file'.
1307Optional argument NON-MARKING determines whether events are created as
e0cd68ee 1308non-marking or not."
81d56594 1309 (interactive "fImport iCalendar data from file:
74692b14 1310Finto diary file:
707c20a8
GM
1311p")
1312 ;; clean up the diary file
1313 (save-current-buffer
707c20a8
GM
1314 ;; now load and convert from the ical file
1315 (set-buffer (find-file ical-filename))
e0cd68ee 1316 (icalendar-import-buffer diary-filename t non-marking)))
707c20a8 1317
86fc29f9 1318;;;###autoload
e0cd68ee
GM
1319(defun icalendar-import-buffer (&optional diary-file do-not-ask
1320 non-marking)
707c20a8
GM
1321 "Extract iCalendar events from current buffer.
1322
1323This function searches the current buffer for the first iCalendar
1324object, reads it and adds all VEVENT elements to the diary
1325DIARY-FILE.
1326
1327It will ask for each appointment whether to add it to the diary
1328when DO-NOT-ASK is non-nil. When called interactively,
1329DO-NOT-ASK is set to t, so that you are asked fore each event.
1330
1331NON-MARKING determines whether diary events are created as
1332non-marking.
1333
74692b14
GM
1334Return code t means that importing worked well, return code nil
1335means that an error has occured. Error messages will be in the
1336buffer `*icalendar-errors*'."
707c20a8
GM
1337 (interactive)
1338 (save-current-buffer
1339 ;; prepare ical
1340 (message "Preparing icalendar...")
e0cd68ee 1341 (set-buffer (icalendar--get-unfolded-buffer (current-buffer)))
707c20a8
GM
1342 (goto-char (point-min))
1343 (message "Preparing icalendar...done")
1344 (if (re-search-forward "^BEGIN:VCALENDAR\\s-*$" nil t)
1345 (let (ical-contents ical-errors)
1346 ;; read ical
1347 (message "Reading icalendar...")
1348 (beginning-of-line)
e0cd68ee 1349 (setq ical-contents (icalendar--read-element nil nil))
707c20a8
GM
1350 (message "Reading icalendar...done")
1351 ;; convert ical
1352 (message "Converting icalendar...")
e0cd68ee 1353 (setq ical-errors (icalendar--convert-ical-to-diary
707c20a8
GM
1354 ical-contents
1355 diary-file do-not-ask non-marking))
1356 (when diary-file
9dd9ed20
GM
1357 ;; save the diary file if it is visited already
1358 (let ((b (find-buffer-visiting diary-file)))
1359 (when b
1360 (save-current-buffer
1361 (set-buffer b)
1362 (save-buffer)))))
707c20a8 1363 (message "Converting icalendar...done")
74692b14
GM
1364 ;; return t if no error occured
1365 (not ical-errors))
707c20a8 1366 (message
74692b14
GM
1367 "Current buffer does not contain icalendar contents!")
1368 ;; return nil, i.e. import did not work
1369 nil)))
707c20a8 1370
e0cd68ee 1371(defalias 'icalendar-extract-ical-from-buffer 'icalendar-import-buffer)
81d56594 1372(make-obsolete 'icalendar-extract-ical-from-buffer 'icalendar-import-buffer)
e0cd68ee 1373
e0cd68ee 1374(defun icalendar--format-ical-event (event)
707c20a8
GM
1375 "Create a string representation of an iCalendar EVENT."
1376 (let ((string icalendar-import-format)
1377 (conversion-list
1378 '(("%d" DESCRIPTION icalendar-import-format-description)
1379 ("%s" SUMMARY icalendar-import-format-subject)
1380 ("%l" LOCATION icalendar-import-format-location)
1381 ("%o" ORGANIZER icalendar-import-format-organizer))))
1382 ;; convert the specifiers in the format string
1383 (mapcar (lambda (i)
1384 (let* ((spec (car i))
1385 (prop (cadr i))
1386 (format (car (cddr i)))
e0cd68ee 1387 (contents (icalendar--get-event-property event prop))
707c20a8 1388 (formatted-contents ""))
707c20a8
GM
1389 (when (and contents (> (length contents) 0))
1390 (setq formatted-contents
e0cd68ee
GM
1391 (icalendar--rris "%s"
1392 (icalendar--convert-string-for-import
1393 contents)
1394 (symbol-value format))))
1395 (setq string (icalendar--rris spec
1396 formatted-contents
1397 string))))
707c20a8
GM
1398 conversion-list)
1399 string))
1400
e0cd68ee
GM
1401(defun icalendar--convert-ical-to-diary (ical-list diary-file
1402 &optional do-not-ask
1403 non-marking)
707c20a8
GM
1404 "Convert an iCalendar file to an Emacs diary file.
1405Import VEVENTS from the iCalendar object ICAL-LIST and saves them to a
1406DIARY-FILE. If DO-NOT-ASK is nil the user is asked for each event
1407whether to actually import it. NON-MARKING determines whether diary
1408events are created as non-marking.
1409This function attempts to return t if something goes wrong. In this
1410case an error string which describes all the errors and problems is
9dd9ed20 1411written into the buffer `*icalendar-errors*'."
e0cd68ee 1412 (let* ((ev (icalendar--all-events ical-list))
707c20a8
GM
1413 (error-string "")
1414 (event-ok t)
1415 (found-error nil)
1416 e diary-string)
1417 ;; step through all events/appointments
1418 (while ev
1419 (setq e (car ev))
1420 (setq ev (cdr ev))
1421 (setq event-ok nil)
1422 (condition-case error-val
9dd9ed20
GM
1423 (let* ((dtstart (icalendar--get-event-property e 'DTSTART))
1424 (dtstart-dec (icalendar--decode-isodatetime dtstart))
74692b14 1425 (start-d (icalendar--datetime-to-diary-date
9dd9ed20
GM
1426 dtstart-dec))
1427 (start-t (icalendar--datetime-to-colontime dtstart-dec))
1428 (dtend (icalendar--get-event-property e 'DTEND))
1429 (dtend-dec (icalendar--decode-isodatetime dtend))
1430 (dtend-1-dec (icalendar--decode-isodatetime dtend -1))
707c20a8 1431 end-d
9dd9ed20 1432 end-1-d
707c20a8 1433 end-t
e0cd68ee
GM
1434 (subject (icalendar--convert-string-for-import
1435 (or (icalendar--get-event-property e 'SUMMARY)
707c20a8 1436 "No Subject")))
e0cd68ee
GM
1437 (rrule (icalendar--get-event-property e 'RRULE))
1438 (rdate (icalendar--get-event-property e 'RDATE))
1439 (duration (icalendar--get-event-property e 'DURATION)))
9dd9ed20 1440 (icalendar--dmsg "%s: `%s'" start-d subject)
74692b14 1441 ;; check whether start-time is missing
9dd9ed20
GM
1442 (if (and dtstart
1443 (string=
1444 (cadr (icalendar--get-event-property-attributes
1445 e 'DTSTART))
1446 "DATE"))
74692b14 1447 (setq start-t nil))
707c20a8 1448 (when duration
9dd9ed20
GM
1449 (let ((dtend-dec-d (icalendar--add-decoded-times
1450 dtstart-dec
1451 (icalendar--decode-isoduration duration)))
1452 (dtend-1-dec-d (icalendar--add-decoded-times
1453 dtstart-dec
1454 (icalendar--decode-isoduration duration
1455 t))))
1456 (if (and dtend-dec (not (eq dtend-dec dtend-dec-d)))
707c20a8
GM
1457 (message "Inconsistent endtime and duration for %s"
1458 subject))
9dd9ed20
GM
1459 (setq dtend-dec dtend-dec-d)
1460 (setq dtend-1-dec dtend-1-dec-d)))
1461 (setq end-d (if dtend-dec
1462 (icalendar--datetime-to-diary-date dtend-dec)
707c20a8 1463 start-d))
9dd9ed20
GM
1464 (setq end-1-d (if dtend-1-dec
1465 (icalendar--datetime-to-diary-date dtend-1-dec)
1466 start-d))
1467 (setq end-t (if (and
1468 dtend-dec
1469 (not (string=
1470 (cadr
1471 (icalendar--get-event-property-attributes
1472 e 'DTEND))
1473 "DATE")))
1474 (icalendar--datetime-to-colontime dtend-dec)
707c20a8 1475 start-t))
e0cd68ee 1476 (icalendar--dmsg "start-d: %s, end-d: %s" start-d end-d)
707c20a8
GM
1477 (cond
1478 ;; recurring event
1479 (rrule
9dd9ed20
GM
1480 (setq diary-string
1481 (icalendar--convert-recurring-to-diary e dtstart-dec start-t
1482 end-t))
1483 (setq event-ok t))
707c20a8 1484 (rdate
e0cd68ee 1485 (icalendar--dmsg "rdate event")
707c20a8
GM
1486 (setq diary-string "")
1487 (mapcar (lambda (datestring)
1488 (setq diary-string
1489 (concat diary-string
1490 (format "......"))))
e0cd68ee 1491 (icalendar--split-value rdate)))
707c20a8 1492 ;; non-recurring event
8ee7eb6b 1493 ;; all-day event
707c20a8 1494 ((not (string= start-d end-d))
9dd9ed20
GM
1495 (setq diary-string
1496 (icalendar--convert-non-recurring-all-day-to-diary
1497 e start-d end-1-d))
707c20a8
GM
1498 (setq event-ok t))
1499 ;; not all-day
1500 ((and start-t (or (not end-t)
1501 (not (string= start-t end-t))))
9dd9ed20
GM
1502 (setq diary-string
1503 (icalendar--convert-non-recurring-not-all-day-to-diary
1504 e dtstart-dec dtend-dec start-t end-t))
707c20a8
GM
1505 (setq event-ok t))
1506 ;; all-day event
1507 (t
e0cd68ee 1508 (icalendar--dmsg "all day event")
74692b14 1509 (setq diary-string (icalendar--datetime-to-diary-date
9dd9ed20 1510 dtstart-dec "/"))
707c20a8
GM
1511 (setq event-ok t)))
1512 ;; add all other elements unless the user doesn't want to have
1513 ;; them
1514 (if event-ok
1515 (progn
1516 (setq diary-string
e0cd68ee
GM
1517 (concat diary-string " "
1518 (icalendar--format-ical-event e)))
707c20a8 1519 (if do-not-ask (setq subject nil))
e0cd68ee
GM
1520 (icalendar--add-diary-entry diary-string diary-file
1521 non-marking subject))
707c20a8
GM
1522 ;; event was not ok
1523 (setq found-error t)
1524 (setq error-string
e0cd68ee
GM
1525 (format "%s\nCannot handle this event:%s"
1526 error-string e))))
74692b14 1527 ;; FIXME: inform user about ignored event properties
707c20a8
GM
1528 ;; handle errors
1529 (error
1530 (message "Ignoring event \"%s\"" e)
1531 (setq found-error t)
74692b14
GM
1532 (setq error-string (format "%s\n%s\nCannot handle this event: %s"
1533 error-val error-string e))
1534 (message error-string))))
707c20a8
GM
1535 (if found-error
1536 (save-current-buffer
9dd9ed20 1537 (set-buffer (get-buffer-create "*icalendar-errors*"))
707c20a8
GM
1538 (erase-buffer)
1539 (insert error-string)))
1540 (message "Converting icalendar...done")
1541 found-error))
1542
9dd9ed20
GM
1543;; subroutines for importing
1544(defun icalendar--convert-recurring-to-diary (e dtstart-dec start-t end-t)
1545 "Convert recurring icalendar event E to diary format.
1546
1547DTSTART-DEC is the DTSTART property of E.
1548START-T is the event's start time in diary format.
1549END-T is the event's end time in diary format."
1550 (icalendar--dmsg "recurring event")
1551 (let* ((rrule (icalendar--get-event-property e 'RRULE))
1552 (rrule-props (icalendar--split-value rrule))
1553 (frequency (cadr (assoc 'FREQ rrule-props)))
1554 (until (cadr (assoc 'UNTIL rrule-props)))
1555 (count (cadr (assoc 'COUNT rrule-props)))
1556 (interval (read (or (cadr (assoc 'INTERVAL rrule-props)) "1")))
1557 (dtstart-conv (icalendar--datetime-to-diary-date dtstart-dec))
1558 (until-conv (icalendar--datetime-to-diary-date
1559 (icalendar--decode-isodatetime until)))
1560 (until-1-conv (icalendar--datetime-to-diary-date
1561 (icalendar--decode-isodatetime until -1)))
1562 (result ""))
1563
1564 ;; FIXME FIXME interval!!!!!!!!!!!!!
1565
1566 (when count
1567 (if until
1568 (message "Must not have UNTIL and COUNT -- ignoring COUNT element!")
1569 (let ((until-1 0))
1570 (cond ((string-equal frequency "DAILY")
1571 (setq until (icalendar--add-decoded-times
1572 dtstart-dec
1573 (list 0 0 0 (* (read count) interval) 0 0)))
1574 (setq until-1 (icalendar--add-decoded-times
1575 dtstart-dec
1576 (list 0 0 0 (* (- (read count) 1) interval)
1577 0 0)))
1578 )
1579 ((string-equal frequency "WEEKLY")
1580 (setq until (icalendar--add-decoded-times
1581 dtstart-dec
1582 (list 0 0 0 (* (read count) 7 interval) 0 0)))
1583 (setq until-1 (icalendar--add-decoded-times
1584 dtstart-dec
1585 (list 0 0 0 (* (- (read count) 1) 7
1586 interval) 0 0)))
1587 )
1588 ((string-equal frequency "MONTHLY")
1589 (setq until (icalendar--add-decoded-times
1590 dtstart-dec (list 0 0 0 0 (* (- (read count) 1)
1591 interval) 0)))
1592 (setq until-1 (icalendar--add-decoded-times
1593 dtstart-dec (list 0 0 0 0 (* (- (read count) 1)
1594 interval) 0)))
1595 )
1596 ((string-equal frequency "YEARLY")
1597 (setq until (icalendar--add-decoded-times
1598 dtstart-dec (list 0 0 0 0 0 (* (- (read count) 1)
1599 interval))))
1600 (setq until-1 (icalendar--add-decoded-times
1601 dtstart-dec
1602 (list 0 0 0 0 0 (* (- (read count) 1)
1603 interval))))
1604 )
1605 (t
1606 (message "Cannot handle COUNT attribute for `%s' events."
1607 frequency)))
1608 (setq until-conv (icalendar--datetime-to-diary-date until))
1609 (setq until-1-conv (icalendar--datetime-to-diary-date until-1))
1610 ))
1611 )
1612 (cond ((string-equal frequency "WEEKLY")
1613 (if (not start-t)
1614 (progn
1615 ;; weekly and all-day
1616 (icalendar--dmsg "weekly all-day")
1617 (if until
1618 (setq result
1619 (format
1620 (concat "%%%%(and "
1621 "(diary-cyclic %d %s) "
1622 "(diary-block %s %s))")
1623 (* interval 7)
1624 dtstart-conv
1625 dtstart-conv
1626 (if count until-1-conv until-conv)
1627 ))
1628 (setq result
1629 (format "%%%%(and (diary-cyclic %d %s))"
1630 (* interval 7)
1631 dtstart-conv))))
1632 ;; weekly and not all-day
1633 (let* ((byday (cadr (assoc 'BYDAY rrule-props)))
1634 (weekday
1635 (icalendar--get-weekday-number byday)))
1636 (icalendar--dmsg "weekly not-all-day")
1637 (if until
1638 (setq result
1639 (format
1640 (concat "%%%%(and "
1641 "(diary-cyclic %d %s) "
1642 "(diary-block %s %s)) "
1643 "%s%s%s")
1644 (* interval 7)
1645 dtstart-conv
1646 dtstart-conv
1647 until-conv
1648 (or start-t "")
1649 (if end-t "-" "") (or end-t "")))
1650 ;; no limit
1651 ;; FIXME!!!!
1652 ;; DTSTART;VALUE=DATE-TIME:20030919T090000
1653 ;; DTEND;VALUE=DATE-TIME:20030919T113000
1654 (setq result
1655 (format
1656 "%%%%(and (diary-cyclic %s %s)) %s%s%s"
1657 (* interval 7)
1658 dtstart-conv
1659 (or start-t "")
1660 (if end-t "-" "") (or end-t "")))))))
1661 ;; yearly
1662 ((string-equal frequency "YEARLY")
1663 (icalendar--dmsg "yearly")
1664 (if until
1665 (setq result (format
1666 (concat "%%%%(and (diary-date %s %s t) "
1667 "(diary-block %s %s)) %s%s%s")
1668 (if european-calendar-style (nth 3 dtstart-dec)
1669 (nth 4 dtstart-dec))
1670 (if european-calendar-style (nth 4 dtstart-dec)
1671 (nth 3 dtstart-dec))
1672 dtstart-conv
1673 until-conv
1674 (or start-t "")
1675 (if end-t "-" "") (or end-t "")))
1676 (setq result (format
1677 "%%%%(and (diary-anniversary %s)) %s%s%s"
1678 dtstart-conv
1679 (or start-t "")
1680 (if end-t "-" "") (or end-t "")))))
1681 ;; monthly
1682 ((string-equal frequency "MONTHLY")
1683 (icalendar--dmsg "monthly")
1684 (setq result
1685 (format
1686 "%%%%(and (diary-date %s %s %s) (diary-block %s %s)) %s%s%s"
1687 (if european-calendar-style (nth 3 dtstart-dec) "t")
1688 (if european-calendar-style "t" (nth 3 dtstart-dec))
1689 "t"
1690 dtstart-conv
1691 (if until
1692 until-conv
1693 "1 1 9999") ;; FIXME: should be unlimited
1694 (or start-t "")
1695 (if end-t "-" "") (or end-t ""))))
1696 ;; daily
1697 ((and (string-equal frequency "DAILY"))
1698 (if until
1699 (setq result
1700 (format
1701 (concat "%%%%(and (diary-cyclic %s %s) "
1702 "(diary-block %s %s)) %s%s%s")
1703 interval dtstart-conv dtstart-conv
1704 (if count until-1-conv until-conv)
1705 (or start-t "")
1706 (if end-t "-" "") (or end-t "")))
1707 (setq result
1708 (format
1709 "%%%%(and (diary-cyclic %s %s)) %s%s%s"
1710 interval
1711 dtstart-conv
1712 (or start-t "")
1713 (if end-t "-" "") (or end-t ""))))))
1714 ;; Handle exceptions from recurrence rules
1715 (let ((ex-dates (icalendar--get-event-properties e 'EXDATE)))
1716 (while ex-dates
1717 (let* ((ex-start (icalendar--decode-isodatetime
1718 (car ex-dates)))
1719 (ex-d (icalendar--datetime-to-diary-date
1720 ex-start)))
1721 (setq result
1722 (icalendar--rris "^%%(\\(and \\)?"
1723 (format
1724 "%%%%(and (not (diary-date %s)) "
1725 ex-d)
1726 result)))
1727 (setq ex-dates (cdr ex-dates))))
1728 ;; FIXME: exception rules are not recognized
1729 (if (icalendar--get-event-property e 'EXRULE)
1730 (setq result
1731 (concat result
1732 "\n Exception rules: "
1733 (icalendar--get-event-properties
1734 e 'EXRULE))))
1735 result))
1736
1737(defun icalendar--convert-non-recurring-all-day-to-diary (event start-d end-d)
1738 "Convert non-recurring icalendar EVENT to diary format.
1739
1740DTSTART is the decoded DTSTART property of E.
1741Argument START-D gives the first day.
1742Argument END-D gives the last day."
1743 (icalendar--dmsg "non-recurring all-day event")
1744 (format "%%%%(and (diary-block %s %s))" start-d end-d))
1745
1746(defun icalendar--convert-non-recurring-not-all-day-to-diary (event dtstart-dec
1747 dtend-dec
1748 start-t
1749 end-t)
1750 "Convert recurring icalendar EVENT to diary format.
1751
1752DTSTART-DEC is the decoded DTSTART property of E.
1753DTEND-DEC is the decoded DTEND property of E.
1754START-T is the event's start time in diary format.
1755END-T is the event's end time in diary format."
1756 (icalendar--dmsg "not all day event")
1757 (cond (end-t
1758 (format "%s %s-%s"
1759 (icalendar--datetime-to-diary-date
1760 dtstart-dec "/")
1761 start-t end-t))
1762 (t
1763 (format "%s %s"
1764 (icalendar--datetime-to-diary-date
1765 dtstart-dec "/")
1766 start-t))))
1767
e0cd68ee
GM
1768(defun icalendar--add-diary-entry (string diary-file non-marking
1769 &optional subject)
707c20a8
GM
1770 "Add STRING to the diary file DIARY-FILE.
1771STRING must be a properly formatted valid diary entry. NON-MARKING
1772determines whether diary events are created as non-marking. If
1773SUBJECT is not nil it must be a string that gives the subject of the
1774entry. In this case the user will be asked whether he wants to insert
1775the entry."
74692b14 1776 (when (or (not subject)
707c20a8 1777 (y-or-n-p (format "Add appointment for `%s' to diary? "
e0cd68ee 1778 subject)))
707c20a8
GM
1779 (when subject
1780 (setq non-marking
1781 (y-or-n-p (format "Make appointment non-marking? "))))
1782 (save-window-excursion
1783 (unless diary-file
1784 (setq diary-file
1785 (read-file-name "Add appointment to this diary file: ")))
1786 (make-diary-entry string non-marking diary-file))))
1787
707c20a8
GM
1788(provide 'icalendar)
1789
a13bc064 1790;; arch-tag: 74fdbe8e-0451-4e38-bb61-4416e822f4fc
707c20a8 1791;;; icalendar.el ends here