1 ;;; icalendar.el --- iCalendar implementation -*-coding: utf-8 -*-
3 ;; Copyright (C) 2002-2011 Free Software Foundation, Inc.
5 ;; Author: Ulf Jasper <ulf.jasper@web.de>
6 ;; Created: August 2002
8 ;; Human-Keywords: calendar, diary, iCalendar, vCalendar
11 ;; This file is part of GNU Emacs.
13 ;; GNU Emacs is free software: you can redistribute it and/or modify
14 ;; it under the terms of the GNU General Public License as published by
15 ;; the Free Software Foundation, either version 3 of the License, or
16 ;; (at your option) any later version.
18 ;; GNU Emacs is distributed in the hope that it will be useful,
19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 ;; GNU General Public License for more details.
23 ;; You should have received a copy of the GNU General Public License
24 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
28 ;; This package is documented in the Emacs Manual.
31 ;; - Diary entries which have a start time but no end time are assumed to
32 ;; last for one hour when they are exported.
33 ;; - Weekly diary entries are assumed to occur the first time in the first
34 ;; week of the year 2000 when they are exported.
35 ;; - Yearly diary entries are assumed to occur the first time in the year
36 ;; 1900 when they are exported.
40 ;; 0.07 onwards: see lisp/ChangeLog
43 ;; - Bugfixes regarding icalendar-import-format-*.
44 ;; - Fix in icalendar-convert-diary-to-ical -- thanks to Philipp Grau.
47 ;; - 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
56 ;; - Bugfix: import: double quoted param values did not work
57 ;; - Read DURATION property when importing.
58 ;; - Added parameter icalendar-duration-correction.
61 ;; - Export takes care of european-calendar-style.
62 ;; - Tested with Emacs 21.3.2 and XEmacs 21.4.12
65 ;; - Should work in XEmacs now. Thanks to Len Trigg for the XEmacs patches!
66 ;; - Added exporting from Emacs diary to ical.
67 ;; - Some bugfixes, after testing with calendars from http://icalshare.com.
68 ;; - Tested with Emacs 21.3.2 and XEmacs 21.4.12
71 ;; - First published version. Trial version. Alpha version.
73 ;; ======================================================================
76 ;; * Import from ical to diary:
77 ;; + Need more properties for icalendar-import-format
78 ;; (added all that Mozilla Calendar uses)
79 ;; From iCal specifications (RFC2445: 4.8.1), icalendar.el lacks
80 ;; ATTACH, CATEGORIES, COMMENT, GEO, PERCENT-COMPLETE (VTODO),
81 ;; PRIORITY, RESOURCES) not considering date/time and time-zone
82 ;; + check vcalendar version
83 ;; + check (unknown) elements
84 ;; + recurring events!
85 ;; + works for european style calendars only! Does it?
87 ;; + exceptions in recurring events
88 ;; + the parser is too soft
89 ;; + error log is incomplete
90 ;; + nice to have: #include "webcal://foo.com/some-calendar.ics"
91 ;; + timezones probably still need some improvements.
93 ;; * Export from diary to ical
94 ;; + diary-date, diary-float, and self-made sexp entries are not
98 ;; + clean up all those date/time parsing functions
99 ;; + Handle todo items?
100 ;; + Check iso 8601 for datetime and period
101 ;; + Which chars to (un)escape?
106 (defconst icalendar-version
"0.19"
107 "Version number of icalendar.el.")
109 ;; ======================================================================
111 ;; ======================================================================
112 (defgroup icalendar nil
117 (defcustom icalendar-import-format
119 "Format for importing events from iCalendar into Emacs diary.
120 It defines how iCalendar events are inserted into diary file.
121 This may either be a string or a function.
123 In case of a formatting STRING the following specifiers can be used:
124 %c Class, see `icalendar-import-format-class'
125 %d Description, see `icalendar-import-format-description'
126 %l Location, see `icalendar-import-format-location'
127 %o Organizer, see `icalendar-import-format-organizer'
128 %s Summary, see `icalendar-import-format-summary'
129 %t Status, see `icalendar-import-format-status'
130 %u URL, see `icalendar-import-format-url'
132 A formatting FUNCTION will be called with a VEVENT as its only
133 argument. It must return a string. See
134 `icalendar-import-format-sample' for an example."
136 (string :tag
"String")
137 (function :tag
"Function"))
140 (defcustom icalendar-import-format-summary
142 "Format string defining how the summary element is formatted.
143 This applies only if the summary is not empty! `%s' is replaced
148 (defcustom icalendar-import-format-description
150 "Format string defining how the description element is formatted.
151 This applies only if the description is not empty! `%s' is
152 replaced by the description."
156 (defcustom icalendar-import-format-location
158 "Format string defining how the location element is formatted.
159 This applies only if the location is not empty! `%s' is replaced
164 (defcustom icalendar-import-format-organizer
166 "Format string defining how the organizer element is formatted.
167 This applies only if the organizer is not empty! `%s' is
168 replaced by the organizer."
172 (defcustom icalendar-import-format-url
174 "Format string defining how the URL element is formatted.
175 This applies only if the URL is not empty! `%s' is replaced by
180 (defcustom icalendar-import-format-status
182 "Format string defining how the status element is formatted.
183 This applies only if the status is not empty! `%s' is replaced by
188 (defcustom icalendar-import-format-class
190 "Format string defining how the class element is formatted.
191 This applies only if the class is not empty! `%s' is replaced by
196 (defcustom icalendar-recurring-start-year
198 "Start year for recurring events.
199 Some calendar browsers only propagate recurring events for
200 several years beyond the start time. Set this string to a year
201 just before the start of your personal calendar."
205 (defcustom icalendar-export-hidden-diary-entries
207 "Determines whether hidden diary entries are exported.
208 If non-nil hidden diary entries (starting with `&') get exported,
209 if nil they are ignored."
213 (defcustom icalendar-uid-format
215 "Format of unique ID code (UID) for each iCalendar object.
216 The following specifiers are available:
217 %c COUNTER, an integer value that is increased each time a uid is
218 generated. This may be necessary for systems which do not
219 provide time-resolution finer than a second.
220 %h HASH, a hash value of the diary entry,
221 %s DTSTART, the start date (excluding time) of the diary entry,
222 %t TIMESTAMP, a unique creation timestamp,
223 %u USERNAME, the variable `user-login-name'.
225 For example, a value of \"%s_%h@mydomain.com\" will generate a
226 UID code for each entry composed of the time of the event, a hash
227 code for the event, and your personal domain name."
231 (defvar icalendar-debug nil
232 "Enable icalendar debug messages.")
234 ;; ======================================================================
235 ;; NO USER SERVICABLE PARTS BELOW THIS LINE
236 ;; ======================================================================
238 (defconst icalendar--weekday-array
["SU" "MO" "TU" "WE" "TH" "FR" "SA"])
240 ;; ======================================================================
241 ;; all the other libs we need
242 ;; ======================================================================
245 ;; ======================================================================
247 ;; ======================================================================
248 (defun icalendar--dmsg (&rest args
)
249 "Print message ARGS if `icalendar-debug' is non-nil."
251 (apply 'message args
)))
253 ;; ======================================================================
254 ;; Core functionality
255 ;; Functions for parsing icalendars, importing and so on
256 ;; ======================================================================
258 (defun icalendar--get-unfolded-buffer (folded-ical-buffer)
259 "Return a new buffer containing the unfolded contents of a buffer.
260 Folding is the iCalendar way of wrapping long lines. In the
261 created buffer all occurrences of CR LF BLANK are replaced by the
262 empty string. Argument FOLDED-ICAL-BUFFER is the unfolded input
264 (let ((unfolded-buffer (get-buffer-create " *icalendar-work*")))
266 (set-buffer unfolded-buffer
)
268 (insert-buffer-substring folded-ical-buffer
)
269 (goto-char (point-min))
270 (while (re-search-forward "\r?\n[ \t]" nil t
)
271 (replace-match "" nil nil
)))
274 (defsubst icalendar--rris
(regexp rep string
&optional fixedcase literal
)
275 "Replace regular expression in string.
276 Pass arguments REGEXP REP STRING FIXEDCASE LITERAL to
277 `replace-regexp-in-string' (Emacs) or to `replace-in-string' (XEmacs)."
278 (cond ((fboundp 'replace-regexp-in-string
)
280 (replace-regexp-in-string regexp rep string fixedcase literal
))
281 ((fboundp 'replace-in-string
)
283 (save-match-data ;; apparently XEmacs needs save-match-data
284 (replace-in-string string regexp rep literal
)))))
286 (defun icalendar--read-element (invalue inparams
)
287 "Recursively read the next iCalendar element in the current buffer.
288 INVALUE gives the current iCalendar element we are reading.
289 INPARAMS gives the current parameters.....
290 This function calls itself recursively for each nested calendar element
292 (let (element children line name params param param-name param-value
297 (re-search-forward "^\\([A-Za-z0-9-]+\\)[;:]" nil t
))
298 (setq name
(intern (match-string 1)))
302 (while (looking-at ";")
303 (re-search-forward ";\\([A-Za-z0-9-]+\\)=" nil nil
)
304 (setq param-name
(intern (match-string 1)))
305 (re-search-forward "\\(\\([^;,:\"]+\\)\\|\"\\([^\"]+\\)\"\\)[;:]"
308 (setq param-value
(or (match-string 2) (match-string 3)))
309 (setq param
(list param-name param-value
))
310 (while (looking-at ",")
311 (re-search-forward "\\(\\([^;,:]+\\)\\|\"\\([^\"]+\\)\"\\)"
314 (setq param-value
(match-string 2))
315 (setq param-value
(match-string 3)))
316 (setq param
(append param param-value
)))
317 (setq params
(append params param
)))
318 (unless (looking-at ":")
321 (re-search-forward "\\(.*\\)\\(\r?\n[ \t].*\\)*" nil t
)
322 (setq value
(icalendar--rris "\r?\n[ \t]" "" (match-string 0)))
323 (setq line
(list name params value
))
324 (cond ((eq name
'BEGIN
)
327 (list (icalendar--read-element (intern value
)
332 (setq element
(append element
(list line
))))))
334 (list invalue inparams element children
)
337 ;; ======================================================================
338 ;; helper functions for examining events
339 ;; ======================================================================
341 ;;(defsubst icalendar--get-all-event-properties (event)
342 ;; "Return the list of properties in this EVENT."
343 ;; (car (cddr event)))
345 (defun icalendar--get-event-property (event prop
)
346 "For the given EVENT return the value of the first occurrence of PROP."
348 (let ((props (car (cddr event
))) pp
)
350 (setq pp
(car props
))
351 (if (eq (car pp
) prop
)
352 (throw 'found
(car (cddr pp
))))
353 (setq props
(cdr props
))))
356 (defun icalendar--get-event-property-attributes (event prop
)
357 "For the given EVENT return attributes of the first occurrence of PROP."
359 (let ((props (car (cddr event
))) pp
)
361 (setq pp
(car props
))
362 (if (eq (car pp
) prop
)
363 (throw 'found
(cadr pp
)))
364 (setq props
(cdr props
))))
367 (defun icalendar--get-event-properties (event prop
)
368 "For the given EVENT return a list of all values of the property PROP."
369 (let ((props (car (cddr event
))) pp result
)
371 (setq pp
(car props
))
372 (if (eq (car pp
) prop
)
373 (setq result
(append (split-string (car (cddr pp
)) ",") result
)))
374 (setq props
(cdr props
)))
377 ;; (defun icalendar--set-event-property (event prop new-value)
378 ;; "For the given EVENT set the property PROP to the value NEW-VALUE."
380 ;; (let ((props (car (cddr event))) pp)
382 ;; (setq pp (car props))
383 ;; (when (eq (car pp) prop)
384 ;; (setcdr (cdr pp) new-value)
385 ;; (throw 'found (car (cddr pp))))
386 ;; (setq props (cdr props)))
387 ;; (setq props (car (cddr event)))
388 ;; (setcar (cddr event)
389 ;; (append props (list (list prop nil new-value)))))))
391 (defun icalendar--get-children (node name
)
392 "Return all children of the given NODE which have a name NAME.
393 For instance the VCALENDAR node can have VEVENT children as well as VTODO
396 (children (cadr (cddr node
))))
397 (when (eq (car node
) name
)
399 ;;(message "%s" node)
404 (icalendar--get-children n name
))
408 (setq result
(append result subresult
))
409 (setq result subresult
)))))
413 (defun icalendar--all-events (icalendar)
414 "Return the list of all existing events in the given ICALENDAR."
415 (icalendar--get-children (car icalendar
) 'VEVENT
))
417 (defun icalendar--split-value (value-string)
418 "Split VALUE-STRING at ';='."
420 param-name param-value
)
423 (set-buffer (get-buffer-create " *icalendar-work*"))
424 (set-buffer-modified-p nil
)
426 (insert value-string
)
427 (goto-char (point-min))
430 "\\([A-Za-z0-9-]+\\)=\\(\\([^;:]+\\)\\|\"\\([^\"]+\\)\"\\);?"
432 (setq param-name
(intern (match-string 1)))
433 (setq param-value
(match-string 2))
435 (append result
(list (list param-name param-value
)))))))
438 (defun icalendar--convert-tz-offset (alist dst-p
)
439 "Return a cons of two strings representing a timezone start.
440 ALIST is an alist entry from a VTIMEZONE, like STANDARD.
441 DST-P is non-nil if this is for daylight savings time.
442 The strings are suitable for assembling into a TZ variable."
443 (let ((offset (car (cddr (assq 'TZOFFSETTO alist
))))
444 (rrule-value (car (cddr (assq 'RRULE alist
))))
445 (dtstart (car (cddr (assq 'DTSTART alist
)))))
446 ;; FIXME: for now we only handle RRULE and not RDATE here.
447 (when (and offset rrule-value dtstart
)
448 (let* ((rrule (icalendar--split-value rrule-value
))
449 (freq (cadr (assq 'FREQ rrule
)))
450 (bymonth (cadr (assq 'BYMONTH rrule
)))
451 (byday (cadr (assq 'BYDAY rrule
))))
452 ;; FIXME: we don't correctly handle WKST here.
453 (if (and (string= freq
"YEARLY") bymonth
)
457 (if dst-p
"DST" "STD")
458 ;; For TZ, OFFSET is added to the local time. So,
459 ;; invert the values.
460 (if (eq (aref offset
0) ?-
) "+" "-")
461 (substring offset
1 3)
463 (substring offset
3 5))
465 (let* ((day (icalendar--get-weekday-number (substring byday -
2)))
466 (week (if (eq day -
1)
468 (substring byday
0 -
2))))
469 ;; "Translate" the icalendar way to specify the last
470 ;; (sun|mon|...)day in month to the tzset way.
471 (if (string= week
"-1") ; last day as icalendar calls it
472 (setq week
"5")) ; last day as tzset calls it
473 (concat "M" bymonth
"." week
"." (if (eq day -
1) "0"
477 (substring dtstart -
6 -
4)
479 (substring dtstart -
4 -
2)
481 (substring dtstart -
2)))))))))
483 (defun icalendar--parse-vtimezone (alist)
484 "Turn a VTIMEZONE ALIST into a cons (ID . TZ-STRING).
485 Return nil if timezone cannot be parsed."
486 (let* ((tz-id (icalendar--get-event-property alist
'TZID
))
487 (daylight (cadr (cdar (icalendar--get-children alist
'DAYLIGHT
))))
488 (day (and daylight
(icalendar--convert-tz-offset daylight t
)))
489 (standard (cadr (cdar (icalendar--get-children alist
'STANDARD
))))
490 (std (and standard
(icalendar--convert-tz-offset standard nil
))))
494 (concat (car std
) (car day
)
495 "," (cdr day
) "," (cdr std
))
498 (defun icalendar--convert-all-timezones (icalendar)
499 "Convert all timezones in the ICALENDAR into an alist.
500 Each element of the alist is a cons (ID . TZ-STRING),
501 like `icalendar--parse-vtimezone'."
503 (dolist (zone (icalendar--get-children (car icalendar
) 'VTIMEZONE
))
504 (setq zone
(icalendar--parse-vtimezone zone
))
506 (setq result
(cons zone result
))))
509 (defun icalendar--find-time-zone (prop-list zone-map
)
510 "Return a timezone string for the time zone in PROP-LIST, or nil if none.
511 ZONE-MAP is a timezone alist as returned by `icalendar--convert-all-timezones'."
512 (let ((id (plist-get prop-list
'TZID
)))
514 (cdr (assoc id zone-map
)))))
516 (defun icalendar--decode-isodatetime (isodatetimestring &optional day-shift
518 "Return ISODATETIMESTRING in format like `decode-time'.
519 Converts from ISO-8601 to Emacs representation. If
520 ISODATETIMESTRING specifies UTC time (trailing letter Z) the
521 decoded time is given in the local time zone! If optional
522 parameter DAY-SHIFT is non-nil the result is shifted by DAY-SHIFT
524 ZONE, if provided, is the timezone, in any format understood by `encode-time'.
526 FIXME: multiple comma-separated values should be allowed!"
527 (icalendar--dmsg isodatetimestring
)
528 (if isodatetimestring
529 ;; day/month/year must be present
530 (let ((year (read (substring isodatetimestring
0 4)))
531 (month (read (substring isodatetimestring
4 6)))
532 (day (read (substring isodatetimestring
6 8)))
536 (when (> (length isodatetimestring
) 12)
537 ;; hour/minute present
538 (setq hour
(read (substring isodatetimestring
9 11)))
539 (setq minute
(read (substring isodatetimestring
11 13))))
540 (when (> (length isodatetimestring
) 14)
542 (setq second
(read (substring isodatetimestring
13 15))))
543 (when (and (> (length isodatetimestring
) 15)
544 ;; UTC specifier present
545 (char-equal ?Z
(aref isodatetimestring
15)))
546 ;; if not UTC add current-time-zone offset
547 (setq second
(+ (car (current-time-zone)) second
)))
548 ;; shift if necessary
550 (let ((mdy (calendar-gregorian-from-absolute
551 (+ (calendar-absolute-from-gregorian
552 (list month day year
))
554 (setq month
(nth 0 mdy
))
555 (setq day
(nth 1 mdy
))
556 (setq year
(nth 2 mdy
))))
557 ;; create the decoded date-time
560 (decode-time (encode-time second minute hour day month year zone
))
562 (message "Cannot decode \"%s\"" isodatetimestring
)
563 ;; hope for the best...
564 (list second minute hour day month year
0 nil
0))))
565 ;; isodatetimestring == nil
568 (defun icalendar--decode-isoduration (isodurationstring
569 &optional duration-correction
)
570 "Convert ISODURATIONSTRING into format provided by `decode-time'.
571 Converts from ISO-8601 to Emacs representation. If ISODURATIONSTRING
572 specifies UTC time (trailing letter Z) the decoded time is given in
575 Optional argument DURATION-CORRECTION shortens result by one day.
577 FIXME: TZID-attributes are ignored....!
578 FIXME: multiple comma-separated values should be allowed!"
579 (if isodurationstring
584 "\\(\\([0-9]+\\)D\\)" ; days only
586 "\\(\\(\\([0-9]+\\)D\\)?T\\(\\([0-9]+\\)H\\)?" ; opt days
587 "\\(\\([0-9]+\\)M\\)?\\(\\([0-9]+\\)S\\)?\\)" ; mand. time
589 "\\(\\([0-9]+\\)W\\)" ; weeks only
590 "\\)$") isodurationstring
)
598 ((match-beginning 2) ;days only
599 (setq days
(read (substring isodurationstring
602 (when duration-correction
603 (setq days
(1- days
))))
604 ((match-beginning 4) ;days and time
605 (if (match-beginning 5)
606 (setq days
(* 7 (read (substring isodurationstring
609 (if (match-beginning 7)
610 (setq hours
(read (substring isodurationstring
613 (if (match-beginning 9)
614 (setq minutes
(read (substring isodurationstring
617 (if (match-beginning 11)
618 (setq seconds
(read (substring isodurationstring
621 ((match-beginning 13) ;weeks only
622 (setq days
(* 7 (read (substring isodurationstring
625 (list seconds minutes hours days months years
)))
626 ;; isodatetimestring == nil
629 (defun icalendar--add-decoded-times (time1 time2
)
631 Both times must be given in decoded form. One of these times must be
632 valid (year > 1900 or something)."
633 ;; FIXME: does this function exist already?
634 (decode-time (encode-time
635 (+ (nth 0 time1
) (nth 0 time2
))
636 (+ (nth 1 time1
) (nth 1 time2
))
637 (+ (nth 2 time1
) (nth 2 time2
))
638 (+ (nth 3 time1
) (nth 3 time2
))
639 (+ (nth 4 time1
) (nth 4 time2
))
640 (+ (nth 5 time1
) (nth 5 time2
))
643 ;;(or (nth 6 time1) (nth 6 time2)) ;; FIXME?
646 (defun icalendar--datetime-to-american-date (datetime &optional separator
)
647 "Convert the decoded DATETIME to American-style format.
648 Optional argument SEPARATOR gives the separator between month,
649 day, and year. If nil a blank character is used as separator.
650 American format: \"month day year\"."
652 (format "%d%s%d%s%d" (nth 4 datetime
) ;month
654 (nth 3 datetime
) ;day
656 (nth 5 datetime
)) ;year
660 (define-obsolete-function-alias 'icalendar--datetime-to-noneuropean-date
661 'icalendar--datetime-to-american-date
"icalendar 0.19")
663 (defun icalendar--datetime-to-european-date (datetime &optional separator
)
664 "Convert the decoded DATETIME to European format.
665 Optional argument SEPARATOR gives the separator between month,
666 day, and year. If nil a blank character is used as separator.
667 European format: (day month year).
670 (format "%d%s%d%s%d" (nth 3 datetime
) ;day
672 (nth 4 datetime
) ;month
674 (nth 5 datetime
)) ;year
678 (defun icalendar--datetime-to-iso-date (datetime &optional separator
)
679 "Convert the decoded DATETIME to ISO format.
680 Optional argument SEPARATOR gives the separator between month,
681 day, and year. If nil a blank character is used as separator.
682 ISO format: (year month day)."
684 (format "%d%s%d%s%d" (nth 5 datetime
) ;year
686 (nth 4 datetime
) ;month
688 (nth 3 datetime
)) ;day
692 (defun icalendar--date-style ()
693 "Return current calendar date style.
694 Convenience function to handle transition from old
695 `european-calendar-style' to new `calendar-date-style'."
696 (if (boundp 'calendar-date-style
)
698 (if (with-no-warnings european-calendar-style
)
702 (defun icalendar--datetime-to-diary-date (datetime &optional separator
)
703 "Convert the decoded DATETIME to diary format.
704 Optional argument SEPARATOR gives the separator between month,
705 day, and year. If nil a blank character is used as separator.
706 Call icalendar--datetime-to-*-date according to the current
707 calendar date style."
708 (funcall (intern-soft (format "icalendar--datetime-to-%s-date"
709 (icalendar--date-style)))
712 (defun icalendar--datetime-to-colontime (datetime)
713 "Extract the time part of a decoded DATETIME into 24-hour format.
714 Note that this silently ignores seconds."
715 (format "%02d:%02d" (nth 2 datetime
) (nth 1 datetime
)))
717 (defun icalendar--get-month-number (monthname)
718 "Return the month number for the given MONTHNAME."
721 (m (downcase monthname
)))
722 (mapc (lambda (month)
723 (let ((mm (downcase month
)))
724 (if (or (string-equal mm m
)
725 (string-equal (substring mm
0 3) m
))
727 (setq num
(1+ num
))))
728 calendar-month-name-array
))
732 (defun icalendar--get-weekday-number (abbrevweekday)
733 "Return the number for the ABBREVWEEKDAY."
737 (aw (downcase abbrevweekday
)))
739 (let ((d (downcase day
)))
740 (if (string-equal d aw
)
742 (setq num
(1+ num
))))
743 icalendar--weekday-array
)))
747 (defun icalendar--get-weekday-numbers (abbrevweekdays)
748 "Return the list of numbers for the comma-separated ABBREVWEEKDAYS."
751 (weekday-alist (mapcar (lambda (day)
754 (cons (downcase day
) num
)))
755 icalendar--weekday-array
)))
757 (mapcar (lambda (abbrevday)
758 (cdr (assoc abbrevday weekday-alist
)))
759 (split-string (downcase abbrevweekdays
) ","))))))
761 (defun icalendar--get-weekday-abbrev (weekday)
762 "Return the abbreviated WEEKDAY."
765 (w (downcase weekday
)))
767 (let ((d (downcase day
)))
768 (if (or (string-equal d w
)
769 (string-equal (substring d
0 3) w
))
770 (throw 'found
(aref icalendar--weekday-array num
)))
771 (setq num
(1+ num
))))
772 calendar-day-name-array
))
776 (defun icalendar--date-to-isodate (date &optional day-shift
)
777 "Convert DATE to iso-style date.
778 DATE must be a list of the form (month day year).
779 If DAY-SHIFT is non-nil, the result is shifted by DAY-SHIFT days."
780 (let ((mdy (calendar-gregorian-from-absolute
781 (+ (calendar-absolute-from-gregorian date
)
783 (format "%04d%02d%02d" (nth 2 mdy
) (nth 0 mdy
) (nth 1 mdy
))))
786 (defun icalendar--datestring-to-isodate (datestring &optional day-shift
)
787 "Convert diary-style DATESTRING to iso-style date.
788 If DAY-SHIFT is non-nil, the result is shifted by DAY-SHIFT days
789 -- DAY-SHIFT must be either nil or an integer. This function
790 tries to figure the date style from DATESTRING itself. If that
791 is not possible it uses the current calendar date style."
792 (let ((day -
1) month year
)
794 (cond ( ;; iso-style numeric date
795 (string-match (concat "\\s-*"
796 "\\([0-9]\\{4\\}\\)[ \t/]\\s-*"
797 "0?\\([1-9][0-9]?\\)[ \t/]\\s-*"
798 "0?\\([1-9][0-9]?\\)")
800 (setq year
(read (substring datestring
(match-beginning 1)
802 (setq month
(read (substring datestring
(match-beginning 2)
804 (setq day
(read (substring datestring
(match-beginning 3)
806 ( ;; non-iso numeric date -- must rely on configured
808 (string-match (concat "\\s-*"
809 "0?\\([1-9][0-9]?\\)[ \t/]\\s-*"
810 "0?\\([1-9][0-9]?\\),?[ \t/]\\s-*"
811 "\\([0-9]\\{4\\}\\)")
813 (setq day
(read (substring datestring
(match-beginning 1)
815 (setq month
(read (substring datestring
(match-beginning 2)
817 (setq year
(read (substring datestring
(match-beginning 3)
819 (if (eq (icalendar--date-style) 'american
)
823 ( ;; date contains month names -- iso style
824 (string-match (concat "\\s-*"
825 "\\([0-9]\\{4\\}\\)[ \t/]\\s-*"
826 "\\([A-Za-z][^ ]+\\)[ \t/]\\s-*"
827 "0?\\([123]?[0-9]\\)")
829 (setq year
(read (substring datestring
(match-beginning 1)
831 (setq month
(icalendar--get-month-number
832 (substring datestring
(match-beginning 2)
834 (setq day
(read (substring datestring
(match-beginning 3)
836 ( ;; date contains month names -- european style
837 (string-match (concat "\\s-*"
838 "0?\\([123]?[0-9]\\)[ \t/]\\s-*"
839 "\\([A-Za-z][^ ]+\\)[ \t/]\\s-*"
840 "\\([0-9]\\{4\\}\\)")
842 (setq day
(read (substring datestring
(match-beginning 1)
844 (setq month
(icalendar--get-month-number
845 (substring datestring
(match-beginning 2)
847 (setq year
(read (substring datestring
(match-beginning 3)
849 ( ;; date contains month names -- american style
850 (string-match (concat "\\s-*"
851 "\\([A-Za-z][^ ]+\\)[ \t/]\\s-*"
852 "0?\\([123]?[0-9]\\),?[ \t/]\\s-*"
853 "\\([0-9]\\{4\\}\\)")
855 (setq day
(read (substring datestring
(match-beginning 2)
857 (setq month
(icalendar--get-month-number
858 (substring datestring
(match-beginning 1)
860 (setq year
(read (substring datestring
(match-beginning 3)
865 (let ((mdy (calendar-gregorian-from-absolute
866 (+ (calendar-absolute-from-gregorian (list month day
869 (icalendar--dmsg (format "%04d%02d%02d" (nth 2 mdy
) (nth 0 mdy
) (nth 1 mdy
)))
870 (format "%04d%02d%02d" (nth 2 mdy
) (nth 0 mdy
) (nth 1 mdy
)))
873 (defun icalendar--diarytime-to-isotime (timestring ampmstring
)
874 "Convert a time like 9:30pm to an iso-conform string like T213000.
875 In this example the TIMESTRING would be \"9:30\" and the AMPMSTRING
878 (let ((starttimenum (read (icalendar--rris ":" "" timestring
))))
879 ;; take care of am/pm style
880 ;; Be sure *not* to convert 12:00pm - 12:59pm to 2400-2459
881 (if (and ampmstring
(string= "pm" ampmstring
) (< starttimenum
1200))
882 (setq starttimenum
(+ starttimenum
1200)))
883 ;; Similar effect with 12:00am - 12:59am (need to convert to 0000-0059)
884 (if (and ampmstring
(string= "am" ampmstring
) (>= starttimenum
1200))
885 (setq starttimenum
(- starttimenum
1200)))
886 (format "T%04d00" starttimenum
))
889 (defun icalendar--convert-string-for-export (string)
890 "Escape comma and other critical characters in STRING."
891 (icalendar--rris "," "\\\\," string
))
893 (defun icalendar--convert-string-for-import (string)
894 "Remove escape chars for comma, semicolon etc. from STRING."
896 "\\\\n" "\n " (icalendar--rris
897 "\\\\\"" "\"" (icalendar--rris
898 "\\\\;" ";" (icalendar--rris
899 "\\\\," "," string
)))))
901 ;; ======================================================================
902 ;; Export -- convert emacs-diary to icalendar
903 ;; ======================================================================
906 (defun icalendar-export-file (diary-filename ical-filename
)
907 "Export diary file to iCalendar format.
908 All diary entries in the file DIARY-FILENAME are converted to iCalendar
909 format. The result is appended to the file ICAL-FILENAME."
910 (interactive "FExport diary data from file:
911 Finto iCalendar file: ")
913 (set-buffer (find-file diary-filename
))
914 (icalendar-export-region (point-min) (point-max) ical-filename
)))
916 (defalias 'icalendar-convert-diary-to-ical
'icalendar-export-file
)
917 (make-obsolete 'icalendar-convert-diary-to-ical
'icalendar-export-file
"22.1")
919 (defvar icalendar--uid-count
0
920 "Auxiliary counter for creating unique ids.")
922 (defun icalendar--create-uid (entry-full contents
)
923 "Construct a unique iCalendar UID for a diary entry.
924 ENTRY-FULL is the full diary entry string. CONTENTS is the
925 current iCalendar object, as a string. Increase
926 `icalendar--uid-count'. Returns the UID string."
927 (let ((uid icalendar-uid-format
))
929 (setq uid
(replace-regexp-in-string
931 (format "%d" icalendar--uid-count
)
933 (setq icalendar--uid-count
(1+ icalendar--uid-count
))
934 (setq uid
(replace-regexp-in-string
936 (format "%d%d%d" (car (current-time))
937 (cadr (current-time))
938 (car (cddr (current-time))))
940 (setq uid
(replace-regexp-in-string
942 (format "%d" (abs (sxhash entry-full
))) uid t t
))
943 (setq uid
(replace-regexp-in-string
944 "%u" (or user-login-name
"UNKNOWN_USER") uid t t
))
945 (let ((dtstart (if (string-match "^DTSTART[^:]*:\\([0-9]*\\)" contents
)
946 (substring contents
(match-beginning 1) (match-end 1))
948 (setq uid
(replace-regexp-in-string "%s" dtstart uid t t
)))
950 ;; Return the UID string
954 (defun icalendar-export-region (min max ical-filename
)
955 "Export region in diary file to iCalendar format.
956 All diary entries in the region from MIN to MAX in the current buffer are
957 converted to iCalendar format. The result is appended to the file
959 This function attempts to return t if something goes wrong. In this
960 case an error string which describes all the errors and problems is
961 written into the buffer `*icalendar-errors*'."
963 FExport diary data into iCalendar file: ")
973 (nonmarker (concat "^" (regexp-quote diary-nonmarking-symbol
)
975 (other-elements nil
))
976 ;; prepare buffer with error messages
978 (set-buffer (get-buffer-create "*icalendar-errors*"))
984 (while (re-search-forward
985 ;; possibly ignore hidden entries beginning with "&"
986 (if icalendar-export-hidden-diary-entries
987 "^\\([^ \t\n#].+\\)\\(\\(\n[ \t].*\\)*\\)"
988 "^\\([^ \t\n&#].+\\)\\(\\(\n[ \t].*\\)*\\)") max t
)
989 (setq entry-main
(match-string 1))
990 (if (match-beginning 2)
991 (setq entry-rest
(match-string 2))
992 (setq entry-rest
""))
993 (setq entry-full
(concat entry-main entry-rest
))
995 (condition-case error-val
997 (setq contents-n-summary
998 (icalendar--convert-to-ical nonmarker entry-main
))
999 (setq other-elements
(icalendar--parse-summary-and-rest
1001 (setq contents
(concat (car contents-n-summary
)
1002 "\nSUMMARY:" (cadr contents-n-summary
)))
1003 (let ((cla (cdr (assoc 'cla other-elements
)))
1004 (des (cdr (assoc 'des other-elements
)))
1005 (loc (cdr (assoc 'loc other-elements
)))
1006 (org (cdr (assoc 'org other-elements
)))
1007 (sta (cdr (assoc 'sta other-elements
)))
1008 (sum (cdr (assoc 'sum other-elements
)))
1009 (url (cdr (assoc 'url other-elements
))))
1011 (setq contents
(concat contents
"\nCLASS:" cla
)))
1013 (setq contents
(concat contents
"\nDESCRIPTION:" des
)))
1015 (setq contents
(concat contents
"\nLOCATION:" loc
)))
1017 (setq contents
(concat contents
"\nORGANIZER:" org
)))
1019 (setq contents
(concat contents
"\nSTATUS:" sta
)))
1021 ;; (setq contents (concat contents "\nSUMMARY:" sum)))
1023 (setq contents
(concat contents
"\nURL:" url
))))
1025 (setq header
(concat "\nBEGIN:VEVENT\nUID:"
1026 (icalendar--create-uid entry-full contents
)))
1027 (setq result
(concat result header contents
"\nEND:VEVENT")))
1030 (setq found-error t
)
1031 (save-current-buffer
1032 (set-buffer (get-buffer-create "*icalendar-errors*"))
1033 (insert (format "Error in line %d -- %s: `%s'\n"
1034 (count-lines (point-min) (point))
1038 ;; we're done, insert everything into the file
1039 (save-current-buffer
1040 (let ((coding-system-for-write 'utf-8
))
1041 (set-buffer (find-file ical-filename
))
1042 (goto-char (point-max))
1043 (insert "BEGIN:VCALENDAR")
1044 (insert "\nPRODID:-//Emacs//NONSGML icalendar.el//EN")
1045 (insert "\nVERSION:2.0")
1047 (insert "\nEND:VCALENDAR\n")
1048 ;; save the diary file
1054 (defun icalendar--convert-to-ical (nonmarker entry-main
)
1055 "Convert a diary entry to icalendar format.
1056 NONMARKER is a regular expression matching the start of non-marking
1057 entries. ENTRY-MAIN is the first line of the diary entry."
1059 ;; anniversaries -- %%(diary-anniversary ...)
1060 (icalendar--convert-anniversary-to-ical nonmarker entry-main
)
1061 ;; cyclic events -- %%(diary-cyclic ...)
1062 (icalendar--convert-cyclic-to-ical nonmarker entry-main
)
1063 ;; diary-date -- %%(diary-date ...)
1064 (icalendar--convert-date-to-ical nonmarker entry-main
)
1065 ;; float events -- %%(diary-float ...)
1066 (icalendar--convert-float-to-ical nonmarker entry-main
)
1067 ;; block events -- %%(diary-block ...)
1068 (icalendar--convert-block-to-ical nonmarker entry-main
)
1069 ;; other sexp diary entries
1070 (icalendar--convert-sexp-to-ical nonmarker entry-main
)
1071 ;; weekly by day -- Monday 8:30 Team meeting
1072 (icalendar--convert-weekly-to-ical nonmarker entry-main
)
1073 ;; yearly by day -- 1 May Tag der Arbeit
1074 (icalendar--convert-yearly-to-ical nonmarker entry-main
)
1075 ;; "ordinary" events, start and end time given
1077 (icalendar--convert-ordinary-to-ical nonmarker entry-main
)
1079 ;; Oops! what's that?
1080 (error "Could not parse entry")))
1082 (defun icalendar--parse-summary-and-rest (summary-and-rest)
1083 "Parse SUMMARY-AND-REST from a diary to fill iCalendar properties.
1086 (if (functionp icalendar-import-format
)
1087 ;; can't do anything
1089 ;; split summary-and-rest
1090 (let* ((s icalendar-import-format
)
1091 (p-cla (or (string-match "%c" icalendar-import-format
) -
1))
1092 (p-des (or (string-match "%d" icalendar-import-format
) -
1))
1093 (p-loc (or (string-match "%l" icalendar-import-format
) -
1))
1094 (p-org (or (string-match "%o" icalendar-import-format
) -
1))
1095 (p-sum (or (string-match "%s" icalendar-import-format
) -
1))
1096 (p-sta (or (string-match "%t" icalendar-import-format
) -
1))
1097 (p-url (or (string-match "%u" icalendar-import-format
) -
1))
1098 (p-list (sort (list p-cla p-des p-loc p-org p-sta p-sum p-url
) '<))
1100 pos-cla pos-des pos-loc pos-org pos-sta pos-sum pos-url
)
1101 (dotimes (i (length p-list
))
1102 ;; Use 'ct' to keep track of current position in list
1103 (cond ((and (>= p-cla
0) (= (nth i p-list
) p-cla
))
1105 (setq pos-cla
(* 2 ct
)))
1106 ((and (>= p-des
0) (= (nth i p-list
) p-des
))
1108 (setq pos-des
(* 2 ct
)))
1109 ((and (>= p-loc
0) (= (nth i p-list
) p-loc
))
1111 (setq pos-loc
(* 2 ct
)))
1112 ((and (>= p-org
0) (= (nth i p-list
) p-org
))
1114 (setq pos-org
(* 2 ct
)))
1115 ((and (>= p-sta
0) (= (nth i p-list
) p-sta
))
1117 (setq pos-sta
(* 2 ct
)))
1118 ((and (>= p-sum
0) (= (nth i p-list
) p-sum
))
1120 (setq pos-sum
(* 2 ct
)))
1121 ((and (>= p-url
0) (= (nth i p-list
) p-url
))
1123 (setq pos-url
(* 2 ct
)))) )
1125 (setq s
(icalendar--rris (car ij
) (cadr ij
) s t t
)))
1127 ;; summary must be first! because of %s
1129 (concat "\\(" icalendar-import-format-summary
"\\)??"))
1131 (concat "\\(" icalendar-import-format-class
"\\)??"))
1133 (concat "\\(" icalendar-import-format-description
"\\)??"))
1135 (concat "\\(" icalendar-import-format-location
"\\)??"))
1137 (concat "\\(" icalendar-import-format-organizer
"\\)??"))
1139 (concat "\\(" icalendar-import-format-status
"\\)??"))
1141 (concat "\\(" icalendar-import-format-url
"\\)??"))))
1142 ;; Need the \' regexp in order to detect multi-line items
1143 (setq s
(concat "\\`"
1144 (icalendar--rris "%s" "\\(.*?\\)" s nil t
)
1146 (if (string-match s summary-and-rest
)
1147 (let (cla des loc org sta sum url
)
1148 (if (and pos-sum
(match-beginning pos-sum
))
1149 (setq sum
(substring summary-and-rest
1150 (match-beginning pos-sum
)
1151 (match-end pos-sum
))))
1152 (if (and pos-cla
(match-beginning pos-cla
))
1153 (setq cla
(substring summary-and-rest
1154 (match-beginning pos-cla
)
1155 (match-end pos-cla
))))
1156 (if (and pos-des
(match-beginning pos-des
))
1157 (setq des
(substring summary-and-rest
1158 (match-beginning pos-des
)
1159 (match-end pos-des
))))
1160 (if (and pos-loc
(match-beginning pos-loc
))
1161 (setq loc
(substring summary-and-rest
1162 (match-beginning pos-loc
)
1163 (match-end pos-loc
))))
1164 (if (and pos-org
(match-beginning pos-org
))
1165 (setq org
(substring summary-and-rest
1166 (match-beginning pos-org
)
1167 (match-end pos-org
))))
1168 (if (and pos-sta
(match-beginning pos-sta
))
1169 (setq sta
(substring summary-and-rest
1170 (match-beginning pos-sta
)
1171 (match-end pos-sta
))))
1172 (if (and pos-url
(match-beginning pos-url
))
1173 (setq url
(substring summary-and-rest
1174 (match-beginning pos-url
)
1175 (match-end pos-url
))))
1176 (list (if cla
(cons 'cla cla
) nil
)
1177 (if des
(cons 'des des
) nil
)
1178 (if loc
(cons 'loc loc
) nil
)
1179 (if org
(cons 'org org
) nil
)
1180 (if sta
(cons 'sta sta
) nil
)
1181 ;;(if sum (cons 'sum sum) nil)
1182 (if url
(cons 'url url
) nil
))))))))
1184 ;; subroutines for icalendar-export-region
1185 (defun icalendar--convert-ordinary-to-ical (nonmarker entry-main
)
1186 "Convert \"ordinary\" diary entry to icalendar format.
1187 NONMARKER is a regular expression matching the start of non-marking
1188 entries. ENTRY-MAIN is the first line of the diary entry."
1191 "\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\)\\s-*" ; date
1192 "\\(\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?" ; start time
1194 "-\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?" ; end time
1196 "\\s-*\\(.*?\\) ?$")
1198 (let* ((datetime (substring entry-main
(match-beginning 1)
1200 (startisostring (icalendar--datestring-to-isodate
1202 (endisostring (icalendar--datestring-to-isodate
1205 (starttimestring (icalendar--diarytime-to-isotime
1206 (if (match-beginning 3)
1207 (substring entry-main
1211 (if (match-beginning 4)
1212 (substring entry-main
1216 (endtimestring (icalendar--diarytime-to-isotime
1217 (if (match-beginning 6)
1218 (substring entry-main
1222 (if (match-beginning 7)
1223 (substring entry-main
1227 (summary (icalendar--convert-string-for-export
1228 (substring entry-main
(match-beginning 8)
1230 (icalendar--dmsg "ordinary %s" entry-main
)
1232 (unless startisostring
1233 (error "Could not parse date"))
1235 ;; If only start-date is specified, then end-date is next day,
1236 ;; otherwise it is same day.
1237 (setq endisostring1
(if starttimestring
1241 (when starttimestring
1242 (unless endtimestring
1244 (read (icalendar--rris "^T0?" ""
1247 ;; Case: ends on same day
1248 (setq endtimestring
(format "T%06d"
1250 ;; Case: ends on next day
1251 (setq endtimestring
(format "T%06d"
1253 (setq endisostring1 endisostring
)) )))
1255 (list (concat "\nDTSTART;"
1256 (if starttimestring
"VALUE=DATE-TIME:"
1259 (or starttimestring
"")
1261 (if endtimestring
"VALUE=DATE-TIME:"
1264 (or endtimestring
""))
1269 (defun icalendar-first-weekday-of-year (abbrevweekday year
)
1270 "Find the first ABBREVWEEKDAY in a given YEAR.
1271 Returns day number."
1272 (let* ((day-of-week-jan01 (calendar-day-of-week (list 1 1 year
)))
1274 (- (icalendar--get-weekday-number abbrevweekday
)
1275 day-of-week-jan01
))))
1276 (cond ((<= result
0)
1277 (setq result
(+ result
7)))
1279 (setq result
(- result
7))))
1282 (defun icalendar--convert-weekly-to-ical (nonmarker entry-main
)
1283 "Convert weekly diary entry to icalendar format.
1284 NONMARKER is a regular expression matching the start of non-marking
1285 entries. ENTRY-MAIN is the first line of the diary entry."
1286 (if (and (string-match (concat nonmarker
1288 "\\(\\([0-9][0-9]?:[0-9][0-9]\\)"
1291 "\\([0-9][0-9]?:[0-9][0-9]\\)"
1294 "\\s-*\\(.*?\\) ?$")
1296 (icalendar--get-weekday-abbrev
1297 (substring entry-main
(match-beginning 1)
1299 (let* ((day (icalendar--get-weekday-abbrev
1300 (substring entry-main
(match-beginning 1)
1302 (starttimestring (icalendar--diarytime-to-isotime
1303 (if (match-beginning 3)
1304 (substring entry-main
1308 (if (match-beginning 4)
1309 (substring entry-main
1313 (endtimestring (icalendar--diarytime-to-isotime
1314 (if (match-beginning 6)
1315 (substring entry-main
1319 (if (match-beginning 7)
1320 (substring entry-main
1324 (summary (icalendar--convert-string-for-export
1325 (substring entry-main
(match-beginning 8)
1327 (icalendar--dmsg "weekly %s" entry-main
)
1329 (when starttimestring
1330 (unless endtimestring
1332 (icalendar--rris "^T0?" ""
1334 (setq endtimestring
(format "T%06d"
1336 (list (concat "\nDTSTART;"
1340 ;; Find the first requested weekday of the
1342 (funcall 'format
"%04d%02d%02d"
1343 icalendar-recurring-start-year
1
1344 (icalendar-first-weekday-of-year
1345 day icalendar-recurring-start-year
))
1346 (or starttimestring
"")
1351 (funcall 'format
"%04d%02d%02d"
1352 ;; end is non-inclusive!
1353 icalendar-recurring-start-year
1
1354 (+ (icalendar-first-weekday-of-year
1355 day icalendar-recurring-start-year
)
1356 (if endtimestring
0 1)))
1357 (or endtimestring
"")
1358 "\nRRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY="
1364 (defun icalendar--convert-yearly-to-ical (nonmarker entry-main
)
1365 "Convert yearly diary entry to icalendar format.
1366 NONMARKER is a regular expression matching the start of non-marking
1367 entries. ENTRY-MAIN is the first line of the diary entry."
1368 (if (string-match (concat nonmarker
1369 (if (eq (icalendar--date-style) 'european
)
1370 "\\([0-9]+[0-9]?\\)\\s-+\\([a-z]+\\)\\s-+"
1371 "\\([a-z]+\\)\\s-+\\([0-9]+[0-9]?\\)\\s-+")
1373 "\\(\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
1375 "-\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1377 "\\s-*\\([^0-9]+.*?\\) ?$" ; must not match years
1380 (let* ((daypos (if (eq (icalendar--date-style) 'european
) 1 2))
1381 (monpos (if (eq (icalendar--date-style) 'european
) 2 1))
1382 (day (read (substring entry-main
1383 (match-beginning daypos
)
1384 (match-end daypos
))))
1385 (month (icalendar--get-month-number
1386 (substring entry-main
1387 (match-beginning monpos
)
1388 (match-end monpos
))))
1389 (starttimestring (icalendar--diarytime-to-isotime
1390 (if (match-beginning 4)
1391 (substring entry-main
1395 (if (match-beginning 5)
1396 (substring entry-main
1400 (endtimestring (icalendar--diarytime-to-isotime
1401 (if (match-beginning 7)
1402 (substring entry-main
1406 (if (match-beginning 8)
1407 (substring entry-main
1411 (summary (icalendar--convert-string-for-export
1412 (substring entry-main
(match-beginning 9)
1414 (icalendar--dmsg "yearly %s" entry-main
)
1416 (when starttimestring
1417 (unless endtimestring
1419 (icalendar--rris "^T0?" ""
1421 (setq endtimestring
(format "T%06d"
1423 (list (concat "\nDTSTART;"
1424 (if starttimestring
"VALUE=DATE-TIME:"
1426 (format "1900%02d%02d" month day
)
1427 (or starttimestring
"")
1429 (if endtimestring
"VALUE=DATE-TIME:"
1431 ;; end is not included! shift by one day
1432 (icalendar--date-to-isodate
1433 (list month day
1900)
1434 (if endtimestring
0 1))
1435 (or endtimestring
"")
1436 "\nRRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH="
1444 (defun icalendar--convert-sexp-to-ical (nonmarker entry-main
)
1445 "Convert complex sexp diary entry to icalendar format -- unsupported!
1449 NONMARKER is a regular expression matching the start of non-marking
1450 entries. ENTRY-MAIN is the first line of the diary entry."
1451 (cond ((string-match (concat nonmarker
1452 "%%(and \\(([^)]+)\\))\\(\\s-*.*?\\) ?$")
1454 ;; simple sexp entry as generated by icalendar.el: strip off the
1455 ;; unnecessary (and)
1456 (icalendar--dmsg "diary-sexp from icalendar.el %s" entry-main
)
1457 (icalendar--convert-to-ical
1460 (substring entry-main
(match-beginning 1) (match-end 1))
1461 (substring entry-main
(match-beginning 2) (match-end 2)))))
1462 ((string-match (concat nonmarker
1465 (icalendar--dmsg "diary-sexp %s" entry-main
)
1466 (error "Sexp-entries are not supported yet"))
1471 (defun icalendar--convert-block-to-ical (nonmarker entry-main
)
1472 "Convert block diary entry to icalendar format.
1473 NONMARKER is a regular expression matching the start of non-marking
1474 entries. ENTRY-MAIN is the first line of the diary entry."
1475 (if (string-match (concat nonmarker
1476 "%%(diary-block \\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\)"
1477 " +\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\))\\s-*"
1478 "\\(\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
1480 "-\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1482 "\\s-*\\(.*?\\) ?$")
1484 (let* ((startstring (substring entry-main
1487 (endstring (substring entry-main
1490 (startisostring (icalendar--datestring-to-isodate
1492 (endisostring (icalendar--datestring-to-isodate
1494 (endisostring+1 (icalendar--datestring-to-isodate
1496 (starttimestring (icalendar--diarytime-to-isotime
1497 (if (match-beginning 4)
1498 (substring entry-main
1502 (if (match-beginning 5)
1503 (substring entry-main
1507 (endtimestring (icalendar--diarytime-to-isotime
1508 (if (match-beginning 7)
1509 (substring entry-main
1513 (if (match-beginning 8)
1514 (substring entry-main
1518 (summary (icalendar--convert-string-for-export
1519 (substring entry-main
(match-beginning 9)
1521 (icalendar--dmsg "diary-block %s" entry-main
)
1522 (when starttimestring
1523 (unless endtimestring
1525 (read (icalendar--rris "^T0?" ""
1527 (setq endtimestring
(format "T%06d"
1530 ;; with time -> write rrule
1531 (list (concat "\nDTSTART;VALUE=DATE-TIME:"
1534 "\nDTEND;VALUE=DATE-TIME:"
1537 "\nRRULE:FREQ=DAILY;INTERVAL=1;UNTIL="
1540 ;; no time -> write long event
1541 (list (concat "\nDTSTART;VALUE=DATE:" startisostring
1542 "\nDTEND;VALUE=DATE:" endisostring
+1)
1547 (defun icalendar--convert-float-to-ical (nonmarker entry-main
)
1548 "Convert float diary entry to icalendar format -- unsupported!
1552 NONMARKER is a regular expression matching the start of non-marking
1553 entries. ENTRY-MAIN is the first line of the diary entry."
1554 (if (string-match (concat nonmarker
1555 "%%(diary-float \\([^)]+\\))\\s-*\\(.*?\\) ?$")
1558 (icalendar--dmsg "diary-float %s" entry-main
)
1559 (error "`diary-float' is not supported yet"))
1563 (defun icalendar--convert-date-to-ical (nonmarker entry-main
)
1564 "Convert `diary-date' diary entry to icalendar format -- unsupported!
1568 NONMARKER is a regular expression matching the start of non-marking
1569 entries. ENTRY-MAIN is the first line of the diary entry."
1570 (if (string-match (concat nonmarker
1571 "%%(diary-date \\([^)]+\\))\\s-*\\(.*?\\) ?$")
1574 (icalendar--dmsg "diary-date %s" entry-main
)
1575 (error "`diary-date' is not supported yet"))
1579 (defun icalendar--convert-cyclic-to-ical (nonmarker entry-main
)
1580 "Convert `diary-cyclic' diary entry to icalendar format.
1581 NONMARKER is a regular expression matching the start of non-marking
1582 entries. ENTRY-MAIN is the first line of the diary entry."
1583 (if (string-match (concat nonmarker
1584 "%%(diary-cyclic \\([^ ]+\\) +"
1585 "\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\))\\s-*"
1586 "\\(\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
1588 "-\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1590 "\\s-*\\(.*?\\) ?$")
1592 (let* ((frequency (substring entry-main
(match-beginning 1)
1594 (datetime (substring entry-main
(match-beginning 2)
1596 (startisostring (icalendar--datestring-to-isodate
1598 (endisostring (icalendar--datestring-to-isodate
1600 (endisostring+1 (icalendar--datestring-to-isodate
1602 (starttimestring (icalendar--diarytime-to-isotime
1603 (if (match-beginning 4)
1604 (substring entry-main
1608 (if (match-beginning 5)
1609 (substring entry-main
1613 (endtimestring (icalendar--diarytime-to-isotime
1614 (if (match-beginning 7)
1615 (substring entry-main
1619 (if (match-beginning 8)
1620 (substring entry-main
1624 (summary (icalendar--convert-string-for-export
1625 (substring entry-main
(match-beginning 9)
1627 (icalendar--dmsg "diary-cyclic %s" entry-main
)
1628 (when starttimestring
1629 (unless endtimestring
1631 (read (icalendar--rris "^T0?" ""
1633 (setq endtimestring
(format "T%06d"
1635 (list (concat "\nDTSTART;"
1636 (if starttimestring
"VALUE=DATE-TIME:"
1639 (or starttimestring
"")
1641 (if endtimestring
"VALUE=DATE-TIME:"
1643 (if endtimestring endisostring endisostring
+1)
1644 (or endtimestring
"")
1645 "\nRRULE:FREQ=DAILY;INTERVAL=" frequency
1646 ;; strange: korganizer does not expect
1647 ;; BYSOMETHING here...
1653 (defun icalendar--convert-anniversary-to-ical (nonmarker entry-main
)
1654 "Convert `diary-anniversary' diary entry to icalendar format.
1655 NONMARKER is a regular expression matching the start of non-marking
1656 entries. ENTRY-MAIN is the first line of the diary entry."
1657 (if (string-match (concat nonmarker
1658 "%%(diary-anniversary \\([^)]+\\))\\s-*"
1659 "\\(\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
1661 "-\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1663 "\\s-*\\(.*?\\) ?$")
1665 (let* ((datetime (substring entry-main
(match-beginning 1)
1667 (startisostring (icalendar--datestring-to-isodate
1669 (endisostring (icalendar--datestring-to-isodate
1671 (starttimestring (icalendar--diarytime-to-isotime
1672 (if (match-beginning 3)
1673 (substring entry-main
1677 (if (match-beginning 4)
1678 (substring entry-main
1682 (endtimestring (icalendar--diarytime-to-isotime
1683 (if (match-beginning 6)
1684 (substring entry-main
1688 (if (match-beginning 7)
1689 (substring entry-main
1693 (summary (icalendar--convert-string-for-export
1694 (substring entry-main
(match-beginning 8)
1696 (icalendar--dmsg "diary-anniversary %s" entry-main
)
1697 (when starttimestring
1698 (unless endtimestring
1700 (read (icalendar--rris "^T0?" ""
1702 (setq endtimestring
(format "T%06d"
1704 (list (concat "\nDTSTART;"
1705 (if starttimestring
"VALUE=DATE-TIME:"
1708 (or starttimestring
"")
1710 (if endtimestring
"VALUE=DATE-TIME:"
1713 (or endtimestring
"")
1714 "\nRRULE:FREQ=YEARLY;INTERVAL=1"
1715 ;; the following is redundant,
1716 ;; but korganizer seems to expect this... ;(
1717 ;; and evolution doesn't understand it... :(
1718 ;; so... who is wrong?!
1720 (substring startisostring
4 6)
1722 (substring startisostring
6 8))
1727 ;; ======================================================================
1728 ;; Import -- convert icalendar to emacs-diary
1729 ;; ======================================================================
1732 (defun icalendar-import-file (ical-filename diary-filename
1733 &optional non-marking
)
1734 "Import an iCalendar file and append to a diary file.
1735 Argument ICAL-FILENAME output iCalendar file.
1736 Argument DIARY-FILENAME input `diary-file'.
1737 Optional argument NON-MARKING determines whether events are created as
1738 non-marking or not."
1739 (interactive "fImport iCalendar data from file:
1742 ;; clean up the diary file
1743 (save-current-buffer
1744 ;; now load and convert from the ical file
1745 (set-buffer (find-file ical-filename
))
1746 (icalendar-import-buffer diary-filename t non-marking
)))
1749 (defun icalendar-import-buffer (&optional diary-file do-not-ask
1751 "Extract iCalendar events from current buffer.
1753 This function searches the current buffer for the first iCalendar
1754 object, reads it and adds all VEVENT elements to the diary
1757 It will ask for each appointment whether to add it to the diary
1758 unless DO-NOT-ASK is non-nil. When called interactively,
1759 DO-NOT-ASK is nil, so that you are asked for each event.
1761 NON-MARKING determines whether diary events are created as
1764 Return code t means that importing worked well, return code nil
1765 means that an error has occurred. Error messages will be in the
1766 buffer `*icalendar-errors*'."
1768 (save-current-buffer
1770 (message "Preparing icalendar...")
1771 (set-buffer (icalendar--get-unfolded-buffer (current-buffer)))
1772 (goto-char (point-min))
1773 (message "Preparing icalendar...done")
1774 (if (re-search-forward "^BEGIN:VCALENDAR\\s-*$" nil t
)
1775 (let (ical-contents ical-errors
)
1777 (message "Reading icalendar...")
1779 (setq ical-contents
(icalendar--read-element nil nil
))
1780 (message "Reading icalendar...done")
1782 (message "Converting icalendar...")
1783 (setq ical-errors
(icalendar--convert-ical-to-diary
1785 diary-file do-not-ask non-marking
))
1787 ;; save the diary file if it is visited already
1788 (let ((b (find-buffer-visiting diary-file
)))
1790 (save-current-buffer
1793 (message "Converting icalendar...done")
1794 ;; return t if no error occurred
1797 "Current buffer does not contain icalendar contents!")
1798 ;; return nil, i.e. import did not work
1801 (defalias 'icalendar-extract-ical-from-buffer
'icalendar-import-buffer
)
1802 (make-obsolete 'icalendar-extract-ical-from-buffer
'icalendar-import-buffer
"22.1")
1804 (defun icalendar--format-ical-event (event)
1805 "Create a string representation of an iCalendar EVENT."
1806 (if (functionp icalendar-import-format
)
1807 (funcall icalendar-import-format event
)
1808 (let ((string icalendar-import-format
)
1810 '(("%c" CLASS icalendar-import-format-class
)
1811 ("%d" DESCRIPTION icalendar-import-format-description
)
1812 ("%l" LOCATION icalendar-import-format-location
)
1813 ("%o" ORGANIZER icalendar-import-format-organizer
)
1814 ("%s" SUMMARY icalendar-import-format-summary
)
1815 ("%t" STATUS icalendar-import-format-status
)
1816 ("%u" URL icalendar-import-format-url
))))
1817 ;; convert the specifiers in the format string
1819 (let* ((spec (car i
))
1821 (format (car (cddr i
)))
1822 (contents (icalendar--get-event-property event prop
))
1823 (formatted-contents ""))
1824 (when (and contents
(> (length contents
) 0))
1825 (setq formatted-contents
1826 (icalendar--rris "%s"
1827 (icalendar--convert-string-for-import
1829 (symbol-value format
)
1831 (setq string
(icalendar--rris spec
1838 (defun icalendar--convert-ical-to-diary (ical-list diary-file
1839 &optional do-not-ask
1841 "Convert iCalendar data to an Emacs diary file.
1842 Import VEVENTS from the iCalendar object ICAL-LIST and saves them to a
1843 DIARY-FILE. If DO-NOT-ASK is nil the user is asked for each event
1844 whether to actually import it. NON-MARKING determines whether diary
1845 events are created as non-marking.
1846 This function attempts to return t if something goes wrong. In this
1847 case an error string which describes all the errors and problems is
1848 written into the buffer `*icalendar-errors*'."
1849 (let* ((ev (icalendar--all-events ical-list
))
1853 (zone-map (icalendar--convert-all-timezones ical-list
))
1855 ;; step through all events/appointments
1860 (condition-case error-val
1861 (let* ((dtstart (icalendar--get-event-property e
'DTSTART
))
1862 (dtstart-zone (icalendar--find-time-zone
1863 (icalendar--get-event-property-attributes
1866 (dtstart-dec (icalendar--decode-isodatetime dtstart nil
1868 (start-d (icalendar--datetime-to-diary-date
1870 (start-t (icalendar--datetime-to-colontime dtstart-dec
))
1871 (dtend (icalendar--get-event-property e
'DTEND
))
1872 (dtend-zone (icalendar--find-time-zone
1873 (icalendar--get-event-property-attributes
1876 (dtend-dec (icalendar--decode-isodatetime dtend
1878 (dtend-1-dec (icalendar--decode-isodatetime dtend -
1
1883 (summary (icalendar--convert-string-for-import
1884 (or (icalendar--get-event-property e
'SUMMARY
)
1886 (rrule (icalendar--get-event-property e
'RRULE
))
1887 (rdate (icalendar--get-event-property e
'RDATE
))
1888 (duration (icalendar--get-event-property e
'DURATION
)))
1889 (icalendar--dmsg "%s: `%s'" start-d summary
)
1890 ;; check whether start-time is missing
1893 (cadr (icalendar--get-event-property-attributes
1898 (let ((dtend-dec-d (icalendar--add-decoded-times
1900 (icalendar--decode-isoduration duration
)))
1901 (dtend-1-dec-d (icalendar--add-decoded-times
1903 (icalendar--decode-isoduration duration
1905 (if (and dtend-dec
(not (eq dtend-dec dtend-dec-d
)))
1906 (message "Inconsistent endtime and duration for %s"
1908 (setq dtend-dec dtend-dec-d
)
1909 (setq dtend-1-dec dtend-1-dec-d
)))
1910 (setq end-d
(if dtend-dec
1911 (icalendar--datetime-to-diary-date dtend-dec
)
1913 (setq end-1-d
(if dtend-1-dec
1914 (icalendar--datetime-to-diary-date dtend-1-dec
)
1916 (setq end-t
(if (and
1920 (icalendar--get-event-property-attributes
1923 (icalendar--datetime-to-colontime dtend-dec
)
1925 (icalendar--dmsg "start-d: %s, end-d: %s" start-d end-d
)
1930 (icalendar--convert-recurring-to-diary e dtstart-dec start-t
1934 (icalendar--dmsg "rdate event")
1935 (setq diary-string
"")
1936 (mapc (lambda (datestring)
1938 (concat diary-string
1939 (format "......"))))
1940 (icalendar--split-value rdate
)))
1941 ;; non-recurring event
1943 ((not (string= start-d end-d
))
1945 (icalendar--convert-non-recurring-all-day-to-diary
1949 ((and start-t
(or (not end-t
)
1950 (not (string= start-t end-t
))))
1952 (icalendar--convert-non-recurring-not-all-day-to-diary
1953 e dtstart-dec dtend-dec start-t end-t
))
1957 (icalendar--dmsg "all day event")
1958 (setq diary-string
(icalendar--datetime-to-diary-date
1961 ;; add all other elements unless the user doesn't want to have
1966 (concat diary-string
" "
1967 (icalendar--format-ical-event e
)))
1968 (if do-not-ask
(setq summary nil
))
1969 ;; add entry to diary and store actual name of diary
1970 ;; file (in case it was nil)
1972 (icalendar--add-diary-entry diary-string diary-file
1973 non-marking summary
)))
1975 (setq found-error t
)
1977 (format "%s\nCannot handle this event:%s"
1979 ;; FIXME: inform user about ignored event properties
1982 (message "Ignoring event \"%s\"" e
)
1983 (setq found-error t
)
1984 (setq error-string
(format "%s\n%s\nCannot handle this event: %s"
1985 error-val error-string e
))
1986 (message "%s" error-string
))))
1988 ;; insert final newline
1990 (let ((b (find-buffer-visiting diary-file
)))
1992 (save-current-buffer
1994 (goto-char (point-max))
1997 (save-current-buffer
1998 (set-buffer (get-buffer-create "*icalendar-errors*"))
2000 (insert error-string
)))
2001 (message "Converting icalendar...done")
2004 ;; subroutines for importing
2005 (defun icalendar--convert-recurring-to-diary (e dtstart-dec start-t end-t
)
2006 "Convert recurring icalendar event E to diary format.
2008 DTSTART-DEC is the DTSTART property of E.
2009 START-T is the event's start time in diary format.
2010 END-T is the event's end time in diary format."
2011 (icalendar--dmsg "recurring event")
2012 (let* ((rrule (icalendar--get-event-property e
'RRULE
))
2013 (rrule-props (icalendar--split-value rrule
))
2014 (frequency (cadr (assoc 'FREQ rrule-props
)))
2015 (until (cadr (assoc 'UNTIL rrule-props
)))
2016 (count (cadr (assoc 'COUNT rrule-props
)))
2017 (interval (read (or (cadr (assoc 'INTERVAL rrule-props
)) "1")))
2018 (dtstart-conv (icalendar--datetime-to-diary-date dtstart-dec
))
2019 (until-conv (icalendar--datetime-to-diary-date
2020 (icalendar--decode-isodatetime until
)))
2021 (until-1-conv (icalendar--datetime-to-diary-date
2022 (icalendar--decode-isodatetime until -
1)))
2025 ;; FIXME FIXME interval!!!!!!!!!!!!!
2029 (message "Must not have UNTIL and COUNT -- ignoring COUNT element!")
2031 (cond ((string-equal frequency
"DAILY")
2032 (setq until
(icalendar--add-decoded-times
2034 (list 0 0 0 (* (read count
) interval
) 0 0)))
2035 (setq until-1
(icalendar--add-decoded-times
2037 (list 0 0 0 (* (- (read count
) 1) interval
)
2040 ((string-equal frequency
"WEEKLY")
2041 (setq until
(icalendar--add-decoded-times
2043 (list 0 0 0 (* (read count
) 7 interval
) 0 0)))
2044 (setq until-1
(icalendar--add-decoded-times
2046 (list 0 0 0 (* (- (read count
) 1) 7
2049 ((string-equal frequency
"MONTHLY")
2050 (setq until
(icalendar--add-decoded-times
2051 dtstart-dec
(list 0 0 0 0 (* (- (read count
) 1)
2053 (setq until-1
(icalendar--add-decoded-times
2054 dtstart-dec
(list 0 0 0 0 (* (- (read count
) 1)
2057 ((string-equal frequency
"YEARLY")
2058 (setq until
(icalendar--add-decoded-times
2059 dtstart-dec
(list 0 0 0 0 0 (* (- (read count
) 1)
2061 (setq until-1
(icalendar--add-decoded-times
2063 (list 0 0 0 0 0 (* (- (read count
) 1)
2067 (message "Cannot handle COUNT attribute for `%s' events."
2069 (setq until-conv
(icalendar--datetime-to-diary-date until
))
2070 (setq until-1-conv
(icalendar--datetime-to-diary-date until-1
))
2073 (cond ((string-equal frequency
"WEEKLY")
2074 (let* ((byday (cadr (assoc 'BYDAY rrule-props
)))
2076 (icalendar--get-weekday-numbers byday
))
2078 (when (> (length weekdays
) 1)
2079 (format "(memq (calendar-day-of-week date) '%s) "
2083 ;; weekly and all-day
2084 (icalendar--dmsg "weekly all-day")
2090 "(diary-block %s %s))")
2092 (format "(diary-cyclic %d %s) "
2096 (if count until-1-conv until-conv
)
2099 (format "%%%%(and %s(diary-cyclic %d %s))"
2100 (or weekday-clause
"")
2101 (if weekday-clause
1 (* interval
7))
2103 ;; weekly and not all-day
2104 (icalendar--dmsg "weekly not-all-day")
2110 "(diary-block %s %s)) "
2113 (format "(diary-cyclic %d %s) "
2119 (if end-t
"-" "") (or end-t
"")))
2122 ;; DTSTART;VALUE=DATE-TIME:20030919T090000
2123 ;; DTEND;VALUE=DATE-TIME:20030919T113000
2126 "%%%%(and %s(diary-cyclic %d %s)) %s%s%s"
2127 (or weekday-clause
"")
2128 (if weekday-clause
1 (* interval
7))
2131 (if end-t
"-" "") (or end-t
"")))))))
2133 ((string-equal frequency
"YEARLY")
2134 (icalendar--dmsg "yearly")
2136 (let ((day (nth 3 dtstart-dec
))
2137 (month (nth 4 dtstart-dec
)))
2138 (setq result
(concat "%%(and (diary-date "
2139 (cond ((eq (icalendar--date-style) 'iso
)
2140 (format "t %d %d" month day
))
2141 ((eq (icalendar--date-style) 'european
)
2142 (format "%d %d t" day month
))
2143 ((eq (icalendar--date-style) 'american
)
2144 (format "%d %d t" month day
)))
2153 (setq result
(format
2154 "%%%%(and (diary-anniversary %s)) %s%s%s"
2157 (if end-t
"-" "") (or end-t
"")))))
2159 ((string-equal frequency
"MONTHLY")
2160 (icalendar--dmsg "monthly")
2163 "%%%%(and (diary-date %s) (diary-block %s %s)) %s%s%s"
2164 (let ((day (nth 3 dtstart-dec
)))
2165 (cond ((eq (icalendar--date-style) 'iso
)
2166 (format "t t %d" day
))
2167 ((eq (icalendar--date-style) 'european
)
2168 (format "%d t t" day
))
2169 ((eq (icalendar--date-style) 'american
)
2170 (format "t %d t" day
))))
2174 (if (eq (icalendar--date-style) 'iso
) "9999 1 1" "1 1 9999")) ;; FIXME: should be unlimited
2176 (if end-t
"-" "") (or end-t
""))))
2178 ((and (string-equal frequency
"DAILY"))
2182 (concat "%%%%(and (diary-cyclic %s %s) "
2183 "(diary-block %s %s)) %s%s%s")
2184 interval dtstart-conv dtstart-conv
2185 (if count until-1-conv until-conv
)
2187 (if end-t
"-" "") (or end-t
"")))
2190 "%%%%(and (diary-cyclic %s %s)) %s%s%s"
2194 (if end-t
"-" "") (or end-t
""))))))
2195 ;; Handle exceptions from recurrence rules
2196 (let ((ex-dates (icalendar--get-event-properties e
'EXDATE
)))
2198 (let* ((ex-start (icalendar--decode-isodatetime
2200 (ex-d (icalendar--datetime-to-diary-date
2203 (icalendar--rris "^%%(\\(and \\)?"
2205 "%%%%(and (not (diary-date %s)) "
2208 (setq ex-dates
(cdr ex-dates
))))
2209 ;; FIXME: exception rules are not recognized
2210 (if (icalendar--get-event-property e
'EXRULE
)
2213 "\n Exception rules: "
2214 (icalendar--get-event-properties
2218 (defun icalendar--convert-non-recurring-all-day-to-diary (event start-d end-d
)
2219 "Convert non-recurring icalendar EVENT to diary format.
2221 DTSTART is the decoded DTSTART property of E.
2222 Argument START-D gives the first day.
2223 Argument END-D gives the last day."
2224 (icalendar--dmsg "non-recurring all-day event")
2225 (format "%%%%(and (diary-block %s %s))" start-d end-d
))
2227 (defun icalendar--convert-non-recurring-not-all-day-to-diary (event dtstart-dec
2231 "Convert recurring icalendar EVENT to diary format.
2233 DTSTART-DEC is the decoded DTSTART property of E.
2234 DTEND-DEC is the decoded DTEND property of E.
2235 START-T is the event's start time in diary format.
2236 END-T is the event's end time in diary format."
2237 (icalendar--dmsg "not all day event")
2240 (icalendar--datetime-to-diary-date
2245 (icalendar--datetime-to-diary-date
2249 (defun icalendar--add-diary-entry (string diary-file non-marking
2251 "Add STRING to the diary file DIARY-FILE.
2252 STRING must be a properly formatted valid diary entry. NON-MARKING
2253 determines whether diary events are created as non-marking. If
2254 SUMMARY is not nil it must be a string that gives the summary of the
2255 entry. In this case the user will be asked whether he wants to insert
2257 (when (or (not summary
)
2258 (y-or-n-p (format "Add appointment for `%s' to diary? "
2262 (y-or-n-p (format "Make appointment non-marking? "))))
2263 (save-window-excursion
2266 (read-file-name "Add appointment to this diary file: ")))
2267 ;; Note: diary-make-entry will add a trailing blank char.... :(
2268 (funcall (if (fboundp 'diary-make-entry
)
2271 string non-marking diary-file
)))
2272 ;; Würgaround to remove the trailing blank char
2273 (with-current-buffer (find-file diary-file
)
2274 (goto-char (point-max))
2275 (if (= (char-before) ?
)
2277 ;; return diary-file in case it has been changed interactively
2280 ;; ======================================================================
2282 ;; ======================================================================
2283 (defun icalendar-import-format-sample (event)
2284 "Example function for formatting an icalendar EVENT."
2285 (format (concat "SUMMARY=`%s' DESCRIPTION=`%s' LOCATION=`%s' ORGANIZER=`%s' "
2286 "STATUS=`%s' URL=`%s' CLASS=`%s'")
2287 (or (icalendar--get-event-property event
'SUMMARY
) "")
2288 (or (icalendar--get-event-property event
'DESCRIPTION
) "")
2289 (or (icalendar--get-event-property event
'LOCATION
) "")
2290 (or (icalendar--get-event-property event
'ORGANIZER
) "")
2291 (or (icalendar--get-event-property event
'STATUS
) "")
2292 (or (icalendar--get-event-property event
'URL
) "")
2293 (or (icalendar--get-event-property event
'CLASS
) "")))
2295 (provide 'icalendar
)
2297 ;;; icalendar.el ends here