1 ;;; icalendar.el --- iCalendar implementation -*-coding: utf-8 -*-
3 ;; Copyright (C) 2002-2012 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.
37 ;; - Float diary entries are assumed to occur the first time on the
38 ;; day when they are exported.
42 ;; 0.07 onwards: see lisp/ChangeLog
45 ;; - Bugfixes regarding icalendar-import-format-*.
46 ;; - Fix in icalendar-convert-diary-to-ical -- thanks to Philipp Grau.
49 ;; - New import format scheme: Replaced icalendar-import-prefix-*,
50 ;; icalendar-import-ignored-properties, and
51 ;; icalendar-import-separator with icalendar-import-format(-*).
52 ;; - icalendar-import-file and icalendar-convert-diary-to-ical
53 ;; have an extra parameter which should prevent them from
54 ;; erasing their target files (untested!).
55 ;; - Tested with Emacs 21.3.2
58 ;; - Bugfix: import: double quoted param values did not work
59 ;; - Read DURATION property when importing.
60 ;; - Added parameter icalendar-duration-correction.
63 ;; - Export takes care of european-calendar-style.
64 ;; - Tested with Emacs 21.3.2 and XEmacs 21.4.12
67 ;; - Should work in XEmacs now. Thanks to Len Trigg for the XEmacs patches!
68 ;; - Added exporting from Emacs diary to ical.
69 ;; - Some bugfixes, after testing with calendars from http://icalshare.com.
70 ;; - Tested with Emacs 21.3.2 and XEmacs 21.4.12
73 ;; - First published version. Trial version. Alpha version.
75 ;; ======================================================================
78 ;; * Import from ical to diary:
79 ;; + Need more properties for icalendar-import-format
80 ;; (added all that Mozilla Calendar uses)
81 ;; From iCal specifications (RFC2445: 4.8.1), icalendar.el lacks
82 ;; ATTACH, CATEGORIES, COMMENT, GEO, PERCENT-COMPLETE (VTODO),
83 ;; PRIORITY, RESOURCES) not considering date/time and time-zone
84 ;; + check vcalendar version
85 ;; + check (unknown) elements
86 ;; + recurring events!
87 ;; + works for european style calendars only! Does it?
89 ;; + exceptions in recurring events
90 ;; + the parser is too soft
91 ;; + error log is incomplete
92 ;; + nice to have: #include "webcal://foo.com/some-calendar.ics"
93 ;; + timezones probably still need some improvements.
95 ;; * Export from diary to ical
96 ;; + diary-date, diary-float, and self-made sexp entries are not
100 ;; + clean up all those date/time parsing functions
101 ;; + Handle todo items?
102 ;; + Check iso 8601 for datetime and period
103 ;; + Which chars to (un)escape?
108 (defconst icalendar-version
"0.19"
109 "Version number of icalendar.el.")
111 ;; ======================================================================
113 ;; ======================================================================
114 (defgroup icalendar nil
119 (defcustom icalendar-import-format
121 "Format for importing events from iCalendar into Emacs diary.
122 It defines how iCalendar events are inserted into diary file.
123 This may either be a string or a function.
125 In case of a formatting STRING the following specifiers can be used:
126 %c Class, see `icalendar-import-format-class'
127 %d Description, see `icalendar-import-format-description'
128 %l Location, see `icalendar-import-format-location'
129 %o Organizer, see `icalendar-import-format-organizer'
130 %s Summary, see `icalendar-import-format-summary'
131 %t Status, see `icalendar-import-format-status'
132 %u URL, see `icalendar-import-format-url'
134 A formatting FUNCTION will be called with a VEVENT as its only
135 argument. It must return a string. See
136 `icalendar-import-format-sample' for an example."
138 (string :tag
"String")
139 (function :tag
"Function"))
142 (defcustom icalendar-import-format-summary
144 "Format string defining how the summary element is formatted.
145 This applies only if the summary is not empty! `%s' is replaced
150 (defcustom icalendar-import-format-description
152 "Format string defining how the description element is formatted.
153 This applies only if the description is not empty! `%s' is
154 replaced by the description."
158 (defcustom icalendar-import-format-location
160 "Format string defining how the location element is formatted.
161 This applies only if the location is not empty! `%s' is replaced
166 (defcustom icalendar-import-format-organizer
168 "Format string defining how the organizer element is formatted.
169 This applies only if the organizer is not empty! `%s' is
170 replaced by the organizer."
174 (defcustom icalendar-import-format-url
176 "Format string defining how the URL element is formatted.
177 This applies only if the URL is not empty! `%s' is replaced by
182 (defcustom icalendar-import-format-status
184 "Format string defining how the status element is formatted.
185 This applies only if the status is not empty! `%s' is replaced by
190 (defcustom icalendar-import-format-class
192 "Format string defining how the class element is formatted.
193 This applies only if the class is not empty! `%s' is replaced by
198 (defcustom icalendar-recurring-start-year
200 "Start year for recurring events.
201 Some calendar browsers only propagate recurring events for
202 several years beyond the start time. Set this string to a year
203 just before the start of your personal calendar."
207 (defcustom icalendar-export-hidden-diary-entries
209 "Determines whether hidden diary entries are exported.
210 If non-nil hidden diary entries (starting with `&') get exported,
211 if nil they are ignored."
215 (defcustom icalendar-uid-format
217 "Format of unique ID code (UID) for each iCalendar object.
218 The following specifiers are available:
219 %c COUNTER, an integer value that is increased each time a uid is
220 generated. This may be necessary for systems which do not
221 provide time-resolution finer than a second.
222 %h HASH, a hash value of the diary entry,
223 %s DTSTART, the start date (excluding time) of the diary entry,
224 %t TIMESTAMP, a unique creation timestamp,
225 %u USERNAME, the variable `user-login-name'.
227 For example, a value of \"%s_%h@mydomain.com\" will generate a
228 UID code for each entry composed of the time of the event, a hash
229 code for the event, and your personal domain name."
233 (defvar icalendar-debug nil
234 "Enable icalendar debug messages.")
236 ;; ======================================================================
237 ;; NO USER SERVICEABLE PARTS BELOW THIS LINE
238 ;; ======================================================================
240 (defconst icalendar--weekday-array
["SU" "MO" "TU" "WE" "TH" "FR" "SA"])
242 ;; ======================================================================
243 ;; all the other libs we need
244 ;; ======================================================================
248 ;; ======================================================================
250 ;; ======================================================================
251 (defun icalendar--dmsg (&rest args
)
252 "Print message ARGS if `icalendar-debug' is non-nil."
254 (apply 'message args
)))
256 ;; ======================================================================
257 ;; Core functionality
258 ;; Functions for parsing icalendars, importing and so on
259 ;; ======================================================================
261 (defun icalendar--get-unfolded-buffer (folded-ical-buffer)
262 "Return a new buffer containing the unfolded contents of a buffer.
263 Folding is the iCalendar way of wrapping long lines. In the
264 created buffer all occurrences of CR LF BLANK are replaced by the
265 empty string. Argument FOLDED-ICAL-BUFFER is the unfolded input
267 (let ((unfolded-buffer (get-buffer-create " *icalendar-work*")))
269 (set-buffer unfolded-buffer
)
271 (insert-buffer-substring folded-ical-buffer
)
272 (goto-char (point-min))
273 (while (re-search-forward "\r?\n[ \t]" nil t
)
274 (replace-match "" nil nil
)))
277 (defsubst icalendar--rris
(regexp rep string
&optional fixedcase literal
)
278 "Replace regular expression in string.
279 Pass arguments REGEXP REP STRING FIXEDCASE LITERAL to
280 `replace-regexp-in-string' (Emacs) or to `replace-in-string' (XEmacs)."
281 (cond ((fboundp 'replace-regexp-in-string
)
283 (replace-regexp-in-string regexp rep string fixedcase literal
))
284 ((fboundp 'replace-in-string
)
286 (save-match-data ;; apparently XEmacs needs save-match-data
287 (replace-in-string string regexp rep literal
)))))
289 (defun icalendar--read-element (invalue inparams
)
290 "Recursively read the next iCalendar element in the current buffer.
291 INVALUE gives the current iCalendar element we are reading.
292 INPARAMS gives the current parameters.....
293 This function calls itself recursively for each nested calendar element
295 (let (element children line name params param param-name param-value
300 (re-search-forward "^\\([A-Za-z0-9-]+\\)[;:]" nil t
))
301 (setq name
(intern (match-string 1)))
305 (while (looking-at ";")
306 (re-search-forward ";\\([A-Za-z0-9-]+\\)=" nil nil
)
307 (setq param-name
(intern (match-string 1)))
308 (re-search-forward "\\(\\([^;,:\"]+\\)\\|\"\\([^\"]+\\)\"\\)[;:]"
311 (setq param-value
(or (match-string 2) (match-string 3)))
312 (setq param
(list param-name param-value
))
313 (while (looking-at ",")
314 (re-search-forward "\\(\\([^;,:]+\\)\\|\"\\([^\"]+\\)\"\\)"
317 (setq param-value
(match-string 2))
318 (setq param-value
(match-string 3)))
319 (setq param
(append param param-value
)))
320 (setq params
(append params param
)))
321 (unless (looking-at ":")
324 (re-search-forward "\\(.*\\)\\(\r?\n[ \t].*\\)*" nil t
)
325 (setq value
(icalendar--rris "\r?\n[ \t]" "" (match-string 0)))
326 (setq line
(list name params value
))
327 (cond ((eq name
'BEGIN
)
330 (list (icalendar--read-element (intern value
)
335 (setq element
(append element
(list line
))))))
337 (list invalue inparams element children
)
340 ;; ======================================================================
341 ;; helper functions for examining events
342 ;; ======================================================================
344 ;;(defsubst icalendar--get-all-event-properties (event)
345 ;; "Return the list of properties in this EVENT."
346 ;; (car (cddr event)))
348 (defun icalendar--get-event-property (event prop
)
349 "For the given EVENT return the value of the first occurrence of PROP."
351 (let ((props (car (cddr event
))) pp
)
353 (setq pp
(car props
))
354 (if (eq (car pp
) prop
)
355 (throw 'found
(car (cddr pp
))))
356 (setq props
(cdr props
))))
359 (defun icalendar--get-event-property-attributes (event prop
)
360 "For the given EVENT return attributes of the first occurrence of PROP."
362 (let ((props (car (cddr event
))) pp
)
364 (setq pp
(car props
))
365 (if (eq (car pp
) prop
)
366 (throw 'found
(cadr pp
)))
367 (setq props
(cdr props
))))
370 (defun icalendar--get-event-properties (event prop
)
371 "For the given EVENT return a list of all values of the property PROP."
372 (let ((props (car (cddr event
))) pp result
)
374 (setq pp
(car props
))
375 (if (eq (car pp
) prop
)
376 (setq result
(append (split-string (car (cddr pp
)) ",") result
)))
377 (setq props
(cdr props
)))
380 ;; (defun icalendar--set-event-property (event prop new-value)
381 ;; "For the given EVENT set the property PROP to the value NEW-VALUE."
383 ;; (let ((props (car (cddr event))) pp)
385 ;; (setq pp (car props))
386 ;; (when (eq (car pp) prop)
387 ;; (setcdr (cdr pp) new-value)
388 ;; (throw 'found (car (cddr pp))))
389 ;; (setq props (cdr props)))
390 ;; (setq props (car (cddr event)))
391 ;; (setcar (cddr event)
392 ;; (append props (list (list prop nil new-value)))))))
394 (defun icalendar--get-children (node name
)
395 "Return all children of the given NODE which have a name NAME.
396 For instance the VCALENDAR node can have VEVENT children as well as VTODO
399 (children (cadr (cddr node
))))
400 (when (eq (car node
) name
)
402 ;;(message "%s" node)
407 (icalendar--get-children n name
))
411 (setq result
(append result subresult
))
412 (setq result subresult
)))))
416 (defun icalendar--all-events (icalendar)
417 "Return the list of all existing events in the given ICALENDAR."
420 (setq result
(append (icalendar--get-children elt
'VEVENT
)
422 (nreverse icalendar
))
425 (defun icalendar--split-value (value-string)
426 "Split VALUE-STRING at ';='."
428 param-name param-value
)
431 (set-buffer (get-buffer-create " *icalendar-work*"))
432 (set-buffer-modified-p nil
)
434 (insert value-string
)
435 (goto-char (point-min))
438 "\\([A-Za-z0-9-]+\\)=\\(\\([^;:]+\\)\\|\"\\([^\"]+\\)\"\\);?"
440 (setq param-name
(intern (match-string 1)))
441 (setq param-value
(match-string 2))
443 (append result
(list (list param-name param-value
)))))))
446 (defun icalendar--convert-tz-offset (alist dst-p
)
447 "Return a cons of two strings representing a timezone start.
448 ALIST is an alist entry from a VTIMEZONE, like STANDARD.
449 DST-P is non-nil if this is for daylight savings time.
450 The strings are suitable for assembling into a TZ variable."
451 (let ((offset (car (cddr (assq 'TZOFFSETTO alist
))))
452 (rrule-value (car (cddr (assq 'RRULE alist
))))
453 (dtstart (car (cddr (assq 'DTSTART alist
)))))
454 ;; FIXME: for now we only handle RRULE and not RDATE here.
455 (when (and offset rrule-value dtstart
)
456 (let* ((rrule (icalendar--split-value rrule-value
))
457 (freq (cadr (assq 'FREQ rrule
)))
458 (bymonth (cadr (assq 'BYMONTH rrule
)))
459 (byday (cadr (assq 'BYDAY rrule
))))
460 ;; FIXME: we don't correctly handle WKST here.
461 (if (and (string= freq
"YEARLY") bymonth
)
465 (if dst-p
"DST" "STD")
466 ;; For TZ, OFFSET is added to the local time. So,
467 ;; invert the values.
468 (if (eq (aref offset
0) ?-
) "+" "-")
469 (substring offset
1 3)
471 (substring offset
3 5))
473 (let* ((day (icalendar--get-weekday-number (substring byday -
2)))
474 (week (if (eq day -
1)
476 (substring byday
0 -
2))))
477 ;; "Translate" the iCalendar way to specify the last
478 ;; (sun|mon|...)day in month to the tzset way.
479 (if (string= week
"-1") ; last day as iCalendar calls it
480 (setq week
"5")) ; last day as tzset calls it
481 (concat "M" bymonth
"." week
"." (if (eq day -
1) "0"
485 (substring dtstart -
6 -
4)
487 (substring dtstart -
4 -
2)
489 (substring dtstart -
2)))))))))
491 (defun icalendar--parse-vtimezone (alist)
492 "Turn a VTIMEZONE ALIST into a cons (ID . TZ-STRING).
493 Return nil if timezone cannot be parsed."
494 (let* ((tz-id (icalendar--get-event-property alist
'TZID
))
495 (daylight (cadr (cdar (icalendar--get-children alist
'DAYLIGHT
))))
496 (day (and daylight
(icalendar--convert-tz-offset daylight t
)))
497 (standard (cadr (cdar (icalendar--get-children alist
'STANDARD
))))
498 (std (and standard
(icalendar--convert-tz-offset standard nil
))))
502 (concat (car std
) (car day
)
503 "," (cdr day
) "," (cdr std
))
506 (defun icalendar--convert-all-timezones (icalendar)
507 "Convert all timezones in the ICALENDAR into an alist.
508 Each element of the alist is a cons (ID . TZ-STRING),
509 like `icalendar--parse-vtimezone'."
511 (dolist (zone (icalendar--get-children (car icalendar
) 'VTIMEZONE
))
512 (setq zone
(icalendar--parse-vtimezone zone
))
514 (setq result
(cons zone result
))))
517 (defun icalendar--find-time-zone (prop-list zone-map
)
518 "Return a timezone string for the time zone in PROP-LIST, or nil if none.
519 ZONE-MAP is a timezone alist as returned by `icalendar--convert-all-timezones'."
520 (let ((id (plist-get prop-list
'TZID
)))
522 (cdr (assoc id zone-map
)))))
524 (defun icalendar--decode-isodatetime (isodatetimestring &optional day-shift
526 "Return ISODATETIMESTRING in format like `decode-time'.
527 Converts from ISO-8601 to Emacs representation. If
528 ISODATETIMESTRING specifies UTC time (trailing letter Z) the
529 decoded time is given in the local time zone! If optional
530 parameter DAY-SHIFT is non-nil the result is shifted by DAY-SHIFT
532 ZONE, if provided, is the timezone, in any format understood by `encode-time'.
534 FIXME: multiple comma-separated values should be allowed!"
535 (icalendar--dmsg isodatetimestring
)
536 (if isodatetimestring
537 ;; day/month/year must be present
538 (let ((year (read (substring isodatetimestring
0 4)))
539 (month (read (substring isodatetimestring
4 6)))
540 (day (read (substring isodatetimestring
6 8)))
544 (when (> (length isodatetimestring
) 12)
545 ;; hour/minute present
546 (setq hour
(read (substring isodatetimestring
9 11)))
547 (setq minute
(read (substring isodatetimestring
11 13))))
548 (when (> (length isodatetimestring
) 14)
550 (setq second
(read (substring isodatetimestring
13 15))))
551 (when (and (> (length isodatetimestring
) 15)
552 ;; UTC specifier present
553 (char-equal ?Z
(aref isodatetimestring
15)))
554 ;; if not UTC add current-time-zone offset
555 (setq second
(+ (car (current-time-zone)) second
)))
556 ;; shift if necessary
558 (let ((mdy (calendar-gregorian-from-absolute
559 (+ (calendar-absolute-from-gregorian
560 (list month day year
))
562 (setq month
(nth 0 mdy
))
563 (setq day
(nth 1 mdy
))
564 (setq year
(nth 2 mdy
))))
565 ;; create the decoded date-time
568 (decode-time (encode-time second minute hour day month year zone
))
570 (message "Cannot decode \"%s\"" isodatetimestring
)
571 ;; hope for the best...
572 (list second minute hour day month year
0 nil
0))))
573 ;; isodatetimestring == nil
576 (defun icalendar--decode-isoduration (isodurationstring
577 &optional duration-correction
)
578 "Convert ISODURATIONSTRING into format provided by `decode-time'.
579 Converts from ISO-8601 to Emacs representation. If ISODURATIONSTRING
580 specifies UTC time (trailing letter Z) the decoded time is given in
583 Optional argument DURATION-CORRECTION shortens result by one day.
585 FIXME: TZID-attributes are ignored....!
586 FIXME: multiple comma-separated values should be allowed!"
587 (if isodurationstring
592 "\\(\\([0-9]+\\)D\\)" ; days only
594 "\\(\\(\\([0-9]+\\)D\\)?T\\(\\([0-9]+\\)H\\)?" ; opt days
595 "\\(\\([0-9]+\\)M\\)?\\(\\([0-9]+\\)S\\)?\\)" ; mand. time
597 "\\(\\([0-9]+\\)W\\)" ; weeks only
598 "\\)$") isodurationstring
)
606 ((match-beginning 2) ;days only
607 (setq days
(read (substring isodurationstring
610 (when duration-correction
611 (setq days
(1- days
))))
612 ((match-beginning 4) ;days and time
613 (if (match-beginning 5)
614 (setq days
(* 7 (read (substring isodurationstring
617 (if (match-beginning 7)
618 (setq hours
(read (substring isodurationstring
621 (if (match-beginning 9)
622 (setq minutes
(read (substring isodurationstring
625 (if (match-beginning 11)
626 (setq seconds
(read (substring isodurationstring
629 ((match-beginning 13) ;weeks only
630 (setq days
(* 7 (read (substring isodurationstring
633 (list seconds minutes hours days months years
)))
634 ;; isodatetimestring == nil
637 (defun icalendar--add-decoded-times (time1 time2
)
639 Both times must be given in decoded form. One of these times must be
640 valid (year > 1900 or something)."
641 ;; FIXME: does this function exist already?
642 (decode-time (encode-time
643 (+ (nth 0 time1
) (nth 0 time2
))
644 (+ (nth 1 time1
) (nth 1 time2
))
645 (+ (nth 2 time1
) (nth 2 time2
))
646 (+ (nth 3 time1
) (nth 3 time2
))
647 (+ (nth 4 time1
) (nth 4 time2
))
648 (+ (nth 5 time1
) (nth 5 time2
))
651 ;;(or (nth 6 time1) (nth 6 time2)) ;; FIXME?
654 (defun icalendar--datetime-to-american-date (datetime &optional separator
)
655 "Convert the decoded DATETIME to American-style format.
656 Optional argument SEPARATOR gives the separator between month,
657 day, and year. If nil a blank character is used as separator.
658 American format: \"month day year\"."
660 (format "%d%s%d%s%d" (nth 4 datetime
) ;month
662 (nth 3 datetime
) ;day
664 (nth 5 datetime
)) ;year
668 (define-obsolete-function-alias 'icalendar--datetime-to-noneuropean-date
669 'icalendar--datetime-to-american-date
"icalendar 0.19")
671 (defun icalendar--datetime-to-european-date (datetime &optional separator
)
672 "Convert the decoded DATETIME to European format.
673 Optional argument SEPARATOR gives the separator between month,
674 day, and year. If nil a blank character is used as separator.
675 European format: (day month year).
678 (format "%d%s%d%s%d" (nth 3 datetime
) ;day
680 (nth 4 datetime
) ;month
682 (nth 5 datetime
)) ;year
686 (defun icalendar--datetime-to-iso-date (datetime &optional separator
)
687 "Convert the decoded DATETIME to ISO format.
688 Optional argument SEPARATOR gives the separator between month,
689 day, and year. If nil a blank character is used as separator.
690 ISO format: (year month day)."
692 (format "%d%s%d%s%d" (nth 5 datetime
) ;year
694 (nth 4 datetime
) ;month
696 (nth 3 datetime
)) ;day
700 (defun icalendar--date-style ()
701 "Return current calendar date style.
702 Convenience function to handle transition from old
703 `european-calendar-style' to new `calendar-date-style'."
704 (if (boundp 'calendar-date-style
)
706 (if (with-no-warnings european-calendar-style
)
710 (defun icalendar--datetime-to-diary-date (datetime &optional separator
)
711 "Convert the decoded DATETIME to diary format.
712 Optional argument SEPARATOR gives the separator between month,
713 day, and year. If nil a blank character is used as separator.
714 Call icalendar--datetime-to-*-date according to the current
715 calendar date style."
716 (funcall (intern-soft (format "icalendar--datetime-to-%s-date"
717 (icalendar--date-style)))
720 (defun icalendar--datetime-to-colontime (datetime)
721 "Extract the time part of a decoded DATETIME into 24-hour format.
722 Note that this silently ignores seconds."
723 (format "%02d:%02d" (nth 2 datetime
) (nth 1 datetime
)))
725 (defun icalendar--get-month-number (monthname)
726 "Return the month number for the given MONTHNAME."
729 (m (downcase monthname
)))
730 (mapc (lambda (month)
731 (let ((mm (downcase month
)))
732 (if (or (string-equal mm m
)
733 (string-equal (substring mm
0 3) m
))
735 (setq num
(1+ num
))))
736 calendar-month-name-array
))
740 (defun icalendar--get-weekday-number (abbrevweekday)
741 "Return the number for the ABBREVWEEKDAY."
745 (aw (downcase abbrevweekday
)))
747 (let ((d (downcase day
)))
748 (if (string-equal d aw
)
750 (setq num
(1+ num
))))
751 icalendar--weekday-array
)))
755 (defun icalendar--get-weekday-numbers (abbrevweekdays)
756 "Return the list of numbers for the comma-separated ABBREVWEEKDAYS."
759 (weekday-alist (mapcar (lambda (day)
762 (cons (downcase day
) num
)))
763 icalendar--weekday-array
)))
765 (mapcar (lambda (abbrevday)
766 (cdr (assoc abbrevday weekday-alist
)))
767 (split-string (downcase abbrevweekdays
) ","))))))
769 (defun icalendar--get-weekday-abbrev (weekday)
770 "Return the abbreviated WEEKDAY."
773 (w (downcase weekday
)))
775 (let ((d (downcase day
)))
776 (if (or (string-equal d w
)
777 (string-equal (substring d
0 3) w
))
778 (throw 'found
(aref icalendar--weekday-array num
)))
779 (setq num
(1+ num
))))
780 calendar-day-name-array
))
784 (defun icalendar--date-to-isodate (date &optional day-shift
)
785 "Convert DATE to iso-style date.
786 DATE must be a list of the form (month day year).
787 If DAY-SHIFT is non-nil, the result is shifted by DAY-SHIFT days."
788 (let ((mdy (calendar-gregorian-from-absolute
789 (+ (calendar-absolute-from-gregorian date
)
791 (format "%04d%02d%02d" (nth 2 mdy
) (nth 0 mdy
) (nth 1 mdy
))))
794 (defun icalendar--datestring-to-isodate (datestring &optional day-shift
)
795 "Convert diary-style DATESTRING to iso-style date.
796 If DAY-SHIFT is non-nil, the result is shifted by DAY-SHIFT days
797 -- DAY-SHIFT must be either nil or an integer. This function
798 tries to figure the date style from DATESTRING itself. If that
799 is not possible it uses the current calendar date style."
800 (let ((day -
1) month year
)
802 (cond ( ;; iso-style numeric date
803 (string-match (concat "\\s-*"
804 "\\([0-9]\\{4\\}\\)[ \t/]\\s-*"
805 "0?\\([1-9][0-9]?\\)[ \t/]\\s-*"
806 "0?\\([1-9][0-9]?\\)")
808 (setq year
(read (substring datestring
(match-beginning 1)
810 (setq month
(read (substring datestring
(match-beginning 2)
812 (setq day
(read (substring datestring
(match-beginning 3)
814 ( ;; non-iso numeric date -- must rely on configured
816 (string-match (concat "\\s-*"
817 "0?\\([1-9][0-9]?\\)[ \t/]\\s-*"
818 "0?\\([1-9][0-9]?\\),?[ \t/]\\s-*"
819 "\\([0-9]\\{4\\}\\)")
821 (setq day
(read (substring datestring
(match-beginning 1)
823 (setq month
(read (substring datestring
(match-beginning 2)
825 (setq year
(read (substring datestring
(match-beginning 3)
827 (if (eq (icalendar--date-style) 'american
)
831 ( ;; date contains month names -- iso style
832 (string-match (concat "\\s-*"
833 "\\([0-9]\\{4\\}\\)[ \t/]\\s-*"
834 "\\([A-Za-z][^ ]+\\)[ \t/]\\s-*"
835 "0?\\([123]?[0-9]\\)")
837 (setq year
(read (substring datestring
(match-beginning 1)
839 (setq month
(icalendar--get-month-number
840 (substring datestring
(match-beginning 2)
842 (setq day
(read (substring datestring
(match-beginning 3)
844 ( ;; date contains month names -- european style
845 (string-match (concat "\\s-*"
846 "0?\\([123]?[0-9]\\)[ \t/]\\s-*"
847 "\\([A-Za-z][^ ]+\\)[ \t/]\\s-*"
848 "\\([0-9]\\{4\\}\\)")
850 (setq day
(read (substring datestring
(match-beginning 1)
852 (setq month
(icalendar--get-month-number
853 (substring datestring
(match-beginning 2)
855 (setq year
(read (substring datestring
(match-beginning 3)
857 ( ;; date contains month names -- american style
858 (string-match (concat "\\s-*"
859 "\\([A-Za-z][^ ]+\\)[ \t/]\\s-*"
860 "0?\\([123]?[0-9]\\),?[ \t/]\\s-*"
861 "\\([0-9]\\{4\\}\\)")
863 (setq day
(read (substring datestring
(match-beginning 2)
865 (setq month
(icalendar--get-month-number
866 (substring datestring
(match-beginning 1)
868 (setq year
(read (substring datestring
(match-beginning 3)
873 (let ((mdy (calendar-gregorian-from-absolute
874 (+ (calendar-absolute-from-gregorian (list month day
877 (icalendar--dmsg (format "%04d%02d%02d" (nth 2 mdy
) (nth 0 mdy
) (nth 1 mdy
)))
878 (format "%04d%02d%02d" (nth 2 mdy
) (nth 0 mdy
) (nth 1 mdy
)))
881 (defun icalendar--diarytime-to-isotime (timestring ampmstring
)
882 "Convert a time like 9:30pm to an iso-conform string like T213000.
883 In this example the TIMESTRING would be \"9:30\" and the AMPMSTRING
886 (let ((starttimenum (read (icalendar--rris ":" "" timestring
))))
887 ;; take care of am/pm style
888 ;; Be sure *not* to convert 12:00pm - 12:59pm to 2400-2459
889 (if (and ampmstring
(string= "pm" ampmstring
) (< starttimenum
1200))
890 (setq starttimenum
(+ starttimenum
1200)))
891 ;; Similar effect with 12:00am - 12:59am (need to convert to 0000-0059)
892 (if (and ampmstring
(string= "am" ampmstring
) (>= starttimenum
1200))
893 (setq starttimenum
(- starttimenum
1200)))
894 (format "T%04d00" starttimenum
))
897 (defun icalendar--convert-string-for-export (string)
898 "Escape comma and other critical characters in STRING."
899 (icalendar--rris "," "\\\\," string
))
901 (defun icalendar--convert-string-for-import (string)
902 "Remove escape chars for comma, semicolon etc. from STRING."
904 "\\\\n" "\n " (icalendar--rris
905 "\\\\\"" "\"" (icalendar--rris
906 "\\\\;" ";" (icalendar--rris
907 "\\\\," "," string
)))))
909 ;; ======================================================================
910 ;; Export -- convert emacs-diary to iCalendar
911 ;; ======================================================================
914 (defun icalendar-export-file (diary-filename ical-filename
)
915 "Export diary file to iCalendar format.
916 All diary entries in the file DIARY-FILENAME are converted to iCalendar
917 format. The result is appended to the file ICAL-FILENAME."
918 (interactive "FExport diary data from file: \n\
919 Finto iCalendar file: ")
921 (set-buffer (find-file diary-filename
))
922 (icalendar-export-region (point-min) (point-max) ical-filename
)))
924 (defalias 'icalendar-convert-diary-to-ical
'icalendar-export-file
)
925 (make-obsolete 'icalendar-convert-diary-to-ical
'icalendar-export-file
"22.1")
927 (defvar icalendar--uid-count
0
928 "Auxiliary counter for creating unique ids.")
930 (defun icalendar--create-uid (entry-full contents
)
931 "Construct a unique iCalendar UID for a diary entry.
932 ENTRY-FULL is the full diary entry string. CONTENTS is the
933 current iCalendar object, as a string. Increase
934 `icalendar--uid-count'. Returns the UID string."
935 (let ((uid icalendar-uid-format
))
937 ;; Allow other apps (such as org-mode) to create its own uid
938 (get-text-property 0 'uid entry-full
)
939 (setq uid
(get-text-property 0 'uid entry-full
))
940 (setq uid
(replace-regexp-in-string
942 (format "%d" icalendar--uid-count
)
944 (setq icalendar--uid-count
(1+ icalendar--uid-count
))
945 (setq uid
(replace-regexp-in-string
947 (format "%d%d%d" (car (current-time))
948 (cadr (current-time))
949 (car (cddr (current-time))))
951 (setq uid
(replace-regexp-in-string
953 (format "%d" (abs (sxhash entry-full
))) uid t t
))
954 (setq uid
(replace-regexp-in-string
955 "%u" (or user-login-name
"UNKNOWN_USER") uid t t
))
956 (let ((dtstart (if (string-match "^DTSTART[^:]*:\\([0-9]*\\)" contents
)
957 (substring contents
(match-beginning 1) (match-end 1))
959 (setq uid
(replace-regexp-in-string "%s" dtstart uid t t
))))
961 ;; Return the UID string
965 (defun icalendar-export-region (min max ical-filename
)
966 "Export region in diary file to iCalendar format.
967 All diary entries in the region from MIN to MAX in the current buffer are
968 converted to iCalendar format. The result is appended to the file
970 This function attempts to return t if something goes wrong. In this
971 case an error string which describes all the errors and problems is
972 written into the buffer `*icalendar-errors*'."
974 FExport diary data into iCalendar file: ")
984 (nonmarker (concat "^" (regexp-quote diary-nonmarking-symbol
)
986 (other-elements nil
))
987 ;; prepare buffer with error messages
989 (set-buffer (get-buffer-create "*icalendar-errors*"))
995 (while (re-search-forward
996 ;; possibly ignore hidden entries beginning with "&"
997 (if icalendar-export-hidden-diary-entries
998 "^\\([^ \t\n#].+\\)\\(\\(\n[ \t].*\\)*\\)"
999 "^\\([^ \t\n&#].+\\)\\(\\(\n[ \t].*\\)*\\)") max t
)
1000 (setq entry-main
(match-string 1))
1001 (if (match-beginning 2)
1002 (setq entry-rest
(match-string 2))
1003 (setq entry-rest
""))
1004 (setq entry-full
(concat entry-main entry-rest
))
1006 (condition-case error-val
1008 (setq contents-n-summary
1009 (icalendar--convert-to-ical nonmarker entry-main
))
1010 (setq other-elements
(icalendar--parse-summary-and-rest
1012 (setq contents
(concat (car contents-n-summary
)
1013 "\nSUMMARY:" (cadr contents-n-summary
)))
1014 (let ((cla (cdr (assoc 'cla other-elements
)))
1015 (des (cdr (assoc 'des other-elements
)))
1016 (loc (cdr (assoc 'loc other-elements
)))
1017 (org (cdr (assoc 'org other-elements
)))
1018 (sta (cdr (assoc 'sta other-elements
)))
1019 (sum (cdr (assoc 'sum other-elements
)))
1020 (url (cdr (assoc 'url other-elements
))))
1022 (setq contents
(concat contents
"\nCLASS:" cla
)))
1024 (setq contents
(concat contents
"\nDESCRIPTION:" des
)))
1026 (setq contents
(concat contents
"\nLOCATION:" loc
)))
1028 (setq contents
(concat contents
"\nORGANIZER:" org
)))
1030 (setq contents
(concat contents
"\nSTATUS:" sta
)))
1032 ;; (setq contents (concat contents "\nSUMMARY:" sum)))
1034 (setq contents
(concat contents
"\nURL:" url
))))
1036 (setq header
(concat "\nBEGIN:VEVENT\nUID:"
1037 (icalendar--create-uid entry-full contents
)))
1038 (setq result
(concat result header contents
"\nEND:VEVENT")))
1041 (setq found-error t
)
1042 (save-current-buffer
1043 (set-buffer (get-buffer-create "*icalendar-errors*"))
1044 (insert (format "Error in line %d -- %s: `%s'\n"
1045 (count-lines (point-min) (point))
1049 ;; we're done, insert everything into the file
1050 (save-current-buffer
1051 (let ((coding-system-for-write 'utf-8
))
1052 (set-buffer (find-file ical-filename
))
1053 (goto-char (point-max))
1054 (insert "BEGIN:VCALENDAR")
1055 (insert "\nPRODID:-//Emacs//NONSGML icalendar.el//EN")
1056 (insert "\nVERSION:2.0")
1058 (insert "\nEND:VCALENDAR\n")
1059 ;; save the diary file
1065 (defun icalendar--convert-to-ical (nonmarker entry-main
)
1066 "Convert a diary entry to iCalendar format.
1067 NONMARKER is a regular expression matching the start of non-marking
1068 entries. ENTRY-MAIN is the first line of the diary entry."
1070 ;; anniversaries -- %%(diary-anniversary ...)
1071 (icalendar--convert-anniversary-to-ical nonmarker entry-main
)
1072 ;; cyclic events -- %%(diary-cyclic ...)
1073 (icalendar--convert-cyclic-to-ical nonmarker entry-main
)
1074 ;; diary-date -- %%(diary-date ...)
1075 (icalendar--convert-date-to-ical nonmarker entry-main
)
1076 ;; float events -- %%(diary-float ...)
1077 (icalendar--convert-float-to-ical nonmarker entry-main
)
1078 ;; block events -- %%(diary-block ...)
1079 (icalendar--convert-block-to-ical nonmarker entry-main
)
1080 ;; other sexp diary entries
1081 (icalendar--convert-sexp-to-ical nonmarker entry-main
)
1082 ;; weekly by day -- Monday 8:30 Team meeting
1083 (icalendar--convert-weekly-to-ical nonmarker entry-main
)
1084 ;; yearly by day -- 1 May Tag der Arbeit
1085 (icalendar--convert-yearly-to-ical nonmarker entry-main
)
1086 ;; "ordinary" events, start and end time given
1088 (icalendar--convert-ordinary-to-ical nonmarker entry-main
)
1090 ;; Oops! what's that?
1091 (error "Could not parse entry")))
1093 (defun icalendar--parse-summary-and-rest (summary-and-rest)
1094 "Parse SUMMARY-AND-REST from a diary to fill iCalendar properties.
1097 (if (functionp icalendar-import-format
)
1098 ;; can't do anything
1100 ;; split summary-and-rest
1101 (let* ((s icalendar-import-format
)
1102 (p-cla (or (string-match "%c" icalendar-import-format
) -
1))
1103 (p-des (or (string-match "%d" icalendar-import-format
) -
1))
1104 (p-loc (or (string-match "%l" icalendar-import-format
) -
1))
1105 (p-org (or (string-match "%o" icalendar-import-format
) -
1))
1106 (p-sum (or (string-match "%s" icalendar-import-format
) -
1))
1107 (p-sta (or (string-match "%t" icalendar-import-format
) -
1))
1108 (p-url (or (string-match "%u" icalendar-import-format
) -
1))
1109 (p-list (sort (list p-cla p-des p-loc p-org p-sta p-sum p-url
) '<))
1111 pos-cla pos-des pos-loc pos-org pos-sta pos-sum pos-url
)
1112 (dotimes (i (length p-list
))
1113 ;; Use 'ct' to keep track of current position in list
1114 (cond ((and (>= p-cla
0) (= (nth i p-list
) p-cla
))
1116 (setq pos-cla
(* 2 ct
)))
1117 ((and (>= p-des
0) (= (nth i p-list
) p-des
))
1119 (setq pos-des
(* 2 ct
)))
1120 ((and (>= p-loc
0) (= (nth i p-list
) p-loc
))
1122 (setq pos-loc
(* 2 ct
)))
1123 ((and (>= p-org
0) (= (nth i p-list
) p-org
))
1125 (setq pos-org
(* 2 ct
)))
1126 ((and (>= p-sta
0) (= (nth i p-list
) p-sta
))
1128 (setq pos-sta
(* 2 ct
)))
1129 ((and (>= p-sum
0) (= (nth i p-list
) p-sum
))
1131 (setq pos-sum
(* 2 ct
)))
1132 ((and (>= p-url
0) (= (nth i p-list
) p-url
))
1134 (setq pos-url
(* 2 ct
)))) )
1136 (setq s
(icalendar--rris (car ij
) (cadr ij
) s t t
)))
1138 ;; summary must be first! because of %s
1140 (concat "\\(" icalendar-import-format-summary
"\\)??"))
1142 (concat "\\(" icalendar-import-format-class
"\\)??"))
1144 (concat "\\(" icalendar-import-format-description
"\\)??"))
1146 (concat "\\(" icalendar-import-format-location
"\\)??"))
1148 (concat "\\(" icalendar-import-format-organizer
"\\)??"))
1150 (concat "\\(" icalendar-import-format-status
"\\)??"))
1152 (concat "\\(" icalendar-import-format-url
"\\)??"))))
1153 ;; Need the \' regexp in order to detect multi-line items
1154 (setq s
(concat "\\`"
1155 (icalendar--rris "%s" "\\(.*?\\)" s nil t
)
1157 (if (string-match s summary-and-rest
)
1158 (let (cla des loc org sta sum url
)
1159 (if (and pos-sum
(match-beginning pos-sum
))
1160 (setq sum
(substring summary-and-rest
1161 (match-beginning pos-sum
)
1162 (match-end pos-sum
))))
1163 (if (and pos-cla
(match-beginning pos-cla
))
1164 (setq cla
(substring summary-and-rest
1165 (match-beginning pos-cla
)
1166 (match-end pos-cla
))))
1167 (if (and pos-des
(match-beginning pos-des
))
1168 (setq des
(substring summary-and-rest
1169 (match-beginning pos-des
)
1170 (match-end pos-des
))))
1171 (if (and pos-loc
(match-beginning pos-loc
))
1172 (setq loc
(substring summary-and-rest
1173 (match-beginning pos-loc
)
1174 (match-end pos-loc
))))
1175 (if (and pos-org
(match-beginning pos-org
))
1176 (setq org
(substring summary-and-rest
1177 (match-beginning pos-org
)
1178 (match-end pos-org
))))
1179 (if (and pos-sta
(match-beginning pos-sta
))
1180 (setq sta
(substring summary-and-rest
1181 (match-beginning pos-sta
)
1182 (match-end pos-sta
))))
1183 (if (and pos-url
(match-beginning pos-url
))
1184 (setq url
(substring summary-and-rest
1185 (match-beginning pos-url
)
1186 (match-end pos-url
))))
1187 (list (if cla
(cons 'cla cla
) nil
)
1188 (if des
(cons 'des des
) nil
)
1189 (if loc
(cons 'loc loc
) nil
)
1190 (if org
(cons 'org org
) nil
)
1191 (if sta
(cons 'sta sta
) nil
)
1192 ;;(if sum (cons 'sum sum) nil)
1193 (if url
(cons 'url url
) nil
))))))))
1195 ;; subroutines for icalendar-export-region
1196 (defun icalendar--convert-ordinary-to-ical (nonmarker entry-main
)
1197 "Convert \"ordinary\" diary entry to iCalendar format.
1198 NONMARKER is a regular expression matching the start of non-marking
1199 entries. ENTRY-MAIN is the first line of the diary entry."
1202 "\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\)\\s-*" ; date
1203 "\\(\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?" ; start time
1205 "-\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?" ; end time
1207 "\\s-*\\(.*?\\) ?$")
1209 (let* ((datetime (substring entry-main
(match-beginning 1)
1211 (startisostring (icalendar--datestring-to-isodate
1213 (endisostring (icalendar--datestring-to-isodate
1216 (starttimestring (icalendar--diarytime-to-isotime
1217 (if (match-beginning 3)
1218 (substring entry-main
1222 (if (match-beginning 4)
1223 (substring entry-main
1227 (endtimestring (icalendar--diarytime-to-isotime
1228 (if (match-beginning 6)
1229 (substring entry-main
1233 (if (match-beginning 7)
1234 (substring entry-main
1238 (summary (icalendar--convert-string-for-export
1239 (substring entry-main
(match-beginning 8)
1241 (icalendar--dmsg "ordinary %s" entry-main
)
1243 (unless startisostring
1244 (error "Could not parse date"))
1246 ;; If only start-date is specified, then end-date is next day,
1247 ;; otherwise it is same day.
1248 (setq endisostring1
(if starttimestring
1252 (when starttimestring
1253 (unless endtimestring
1255 (read (icalendar--rris "^T0?" ""
1258 ;; Case: ends on same day
1259 (setq endtimestring
(format "T%06d"
1261 ;; Case: ends on next day
1262 (setq endtimestring
(format "T%06d"
1264 (setq endisostring1 endisostring
)) )))
1266 (list (concat "\nDTSTART;"
1267 (if starttimestring
"VALUE=DATE-TIME:"
1270 (or starttimestring
"")
1272 (if endtimestring
"VALUE=DATE-TIME:"
1275 (or endtimestring
""))
1280 (defun icalendar-first-weekday-of-year (abbrevweekday year
)
1281 "Find the first ABBREVWEEKDAY in a given YEAR.
1282 Returns day number."
1283 (let* ((day-of-week-jan01 (calendar-day-of-week (list 1 1 year
)))
1285 (- (icalendar--get-weekday-number abbrevweekday
)
1286 day-of-week-jan01
))))
1287 (cond ((<= result
0)
1288 (setq result
(+ result
7)))
1290 (setq result
(- result
7))))
1293 (defun icalendar--convert-weekly-to-ical (nonmarker entry-main
)
1294 "Convert weekly diary entry to iCalendar format.
1295 NONMARKER is a regular expression matching the start of non-marking
1296 entries. ENTRY-MAIN is the first line of the diary entry."
1297 (if (and (string-match (concat nonmarker
1299 "\\(\\([0-9][0-9]?:[0-9][0-9]\\)"
1302 "\\([0-9][0-9]?:[0-9][0-9]\\)"
1305 "\\s-*\\(.*?\\) ?$")
1307 (icalendar--get-weekday-abbrev
1308 (substring entry-main
(match-beginning 1)
1310 (let* ((day (icalendar--get-weekday-abbrev
1311 (substring entry-main
(match-beginning 1)
1313 (starttimestring (icalendar--diarytime-to-isotime
1314 (if (match-beginning 3)
1315 (substring entry-main
1319 (if (match-beginning 4)
1320 (substring entry-main
1324 (endtimestring (icalendar--diarytime-to-isotime
1325 (if (match-beginning 6)
1326 (substring entry-main
1330 (if (match-beginning 7)
1331 (substring entry-main
1335 (summary (icalendar--convert-string-for-export
1336 (substring entry-main
(match-beginning 8)
1338 (icalendar--dmsg "weekly %s" entry-main
)
1340 (when starttimestring
1341 (unless endtimestring
1343 (icalendar--rris "^T0?" ""
1345 (setq endtimestring
(format "T%06d"
1347 (list (concat "\nDTSTART;"
1351 ;; Find the first requested weekday of the
1353 (funcall 'format
"%04d%02d%02d"
1354 icalendar-recurring-start-year
1
1355 (icalendar-first-weekday-of-year
1356 day icalendar-recurring-start-year
))
1357 (or starttimestring
"")
1362 (funcall 'format
"%04d%02d%02d"
1363 ;; end is non-inclusive!
1364 icalendar-recurring-start-year
1
1365 (+ (icalendar-first-weekday-of-year
1366 day icalendar-recurring-start-year
)
1367 (if endtimestring
0 1)))
1368 (or endtimestring
"")
1369 "\nRRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY="
1375 (defun icalendar--convert-yearly-to-ical (nonmarker entry-main
)
1376 "Convert yearly diary entry to iCalendar format.
1377 NONMARKER is a regular expression matching the start of non-marking
1378 entries. ENTRY-MAIN is the first line of the diary entry."
1379 (if (string-match (concat nonmarker
1380 (if (eq (icalendar--date-style) 'european
)
1381 "\\([0-9]+[0-9]?\\)\\s-+\\([a-z]+\\)\\s-+"
1382 "\\([a-z]+\\)\\s-+\\([0-9]+[0-9]?\\)\\s-+")
1384 "\\(\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
1386 "-\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1388 "\\s-*\\([^0-9]+.*?\\) ?$" ; must not match years
1391 (let* ((daypos (if (eq (icalendar--date-style) 'european
) 1 2))
1392 (monpos (if (eq (icalendar--date-style) 'european
) 2 1))
1393 (day (read (substring entry-main
1394 (match-beginning daypos
)
1395 (match-end daypos
))))
1396 (month (icalendar--get-month-number
1397 (substring entry-main
1398 (match-beginning monpos
)
1399 (match-end monpos
))))
1400 (starttimestring (icalendar--diarytime-to-isotime
1401 (if (match-beginning 4)
1402 (substring entry-main
1406 (if (match-beginning 5)
1407 (substring entry-main
1411 (endtimestring (icalendar--diarytime-to-isotime
1412 (if (match-beginning 7)
1413 (substring entry-main
1417 (if (match-beginning 8)
1418 (substring entry-main
1422 (summary (icalendar--convert-string-for-export
1423 (substring entry-main
(match-beginning 9)
1425 (icalendar--dmsg "yearly %s" entry-main
)
1427 (when starttimestring
1428 (unless endtimestring
1430 (icalendar--rris "^T0?" ""
1432 (setq endtimestring
(format "T%06d"
1434 (list (concat "\nDTSTART;"
1435 (if starttimestring
"VALUE=DATE-TIME:"
1437 (format "1900%02d%02d" month day
)
1438 (or starttimestring
"")
1440 (if endtimestring
"VALUE=DATE-TIME:"
1442 ;; end is not included! shift by one day
1443 (icalendar--date-to-isodate
1444 (list month day
1900)
1445 (if endtimestring
0 1))
1446 (or endtimestring
"")
1447 "\nRRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH="
1455 (defun icalendar--convert-sexp-to-ical (nonmarker entry-main
)
1456 "Convert complex sexp diary entry to iCalendar format -- unsupported!
1460 NONMARKER is a regular expression matching the start of non-marking
1461 entries. ENTRY-MAIN is the first line of the diary entry."
1462 (cond ((string-match (concat nonmarker
1463 "%%(and \\(([^)]+)\\))\\(\\s-*.*?\\) ?$")
1465 ;; simple sexp entry as generated by icalendar.el: strip off the
1466 ;; unnecessary (and)
1467 (icalendar--dmsg "diary-sexp from icalendar.el %s" entry-main
)
1468 (icalendar--convert-to-ical
1471 (substring entry-main
(match-beginning 1) (match-end 1))
1472 (substring entry-main
(match-beginning 2) (match-end 2)))))
1473 ((string-match (concat nonmarker
1476 (icalendar--dmsg "diary-sexp %s" entry-main
)
1477 (error "Sexp-entries are not supported yet"))
1482 (defun icalendar--convert-block-to-ical (nonmarker entry-main
)
1483 "Convert block diary entry to iCalendar format.
1484 NONMARKER is a regular expression matching the start of non-marking
1485 entries. ENTRY-MAIN is the first line of the diary entry."
1486 (if (string-match (concat nonmarker
1487 "%%(diary-block \\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\)"
1488 " +\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\))\\s-*"
1489 "\\(\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
1491 "-\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1493 "\\s-*\\(.*?\\) ?$")
1495 (let* ((startstring (substring entry-main
1498 (endstring (substring entry-main
1501 (startisostring (icalendar--datestring-to-isodate
1503 (endisostring (icalendar--datestring-to-isodate
1505 (endisostring+1 (icalendar--datestring-to-isodate
1507 (starttimestring (icalendar--diarytime-to-isotime
1508 (if (match-beginning 4)
1509 (substring entry-main
1513 (if (match-beginning 5)
1514 (substring entry-main
1518 (endtimestring (icalendar--diarytime-to-isotime
1519 (if (match-beginning 7)
1520 (substring entry-main
1524 (if (match-beginning 8)
1525 (substring entry-main
1529 (summary (icalendar--convert-string-for-export
1530 (substring entry-main
(match-beginning 9)
1532 (icalendar--dmsg "diary-block %s" entry-main
)
1533 (when starttimestring
1534 (unless endtimestring
1536 (read (icalendar--rris "^T0?" ""
1538 (setq endtimestring
(format "T%06d"
1541 ;; with time -> write rrule
1542 (list (concat "\nDTSTART;VALUE=DATE-TIME:"
1545 "\nDTEND;VALUE=DATE-TIME:"
1548 "\nRRULE:FREQ=DAILY;INTERVAL=1;UNTIL="
1551 ;; no time -> write long event
1552 (list (concat "\nDTSTART;VALUE=DATE:" startisostring
1553 "\nDTEND;VALUE=DATE:" endisostring
+1)
1558 (defun icalendar--convert-float-to-ical (nonmarker entry-main
)
1559 "Convert float diary entry to iCalendar format -- partially unsupported!
1561 FIXME! DAY from diary-float yet unimplemented.
1563 NONMARKER is a regular expression matching the start of non-marking
1564 entries. ENTRY-MAIN is the first line of the diary entry."
1565 (if (string-match (concat nonmarker
"%%\\((diary-float .+\\) ?$") entry-main
)
1567 (insert (match-string 1 entry-main
))
1568 (goto-char (point-min))
1569 (let* ((sexp (read (current-buffer))) ;using `read' here
1571 ;matching, esp. with
1574 (month (nth 1 sexp
))
1575 (dayname (nth 2 sexp
))
1579 (replace-regexp-in-string
1580 "\\(^\s+\\|\s+$\\)" ""
1581 (buffer-substring (point) (point-max)))))
1585 (icalendar--dmsg "diary-float %s" entry-main
)
1586 (error "Don't know if or how to implement day in `diary-float'")))
1589 ;;Start today (yes this is an arbitrary choice):
1590 "\nDTSTART;VALUE=DATE:"
1591 (format-time-string "%Y%m%d" (current-time))
1592 ;;BUT remove today if `diary-float'
1593 ;;expression does not hold true for today:
1595 (null (let ((date (calendar-current-date))
1597 (diary-float month dayname n
)))
1599 "\nEXDATE;VALUE=DATE:"
1600 (format-time-string "%Y%m%d" (current-time))))
1602 (if (or (numberp month
) (listp month
))
1603 "FREQ=YEARLY;BYMONTH="
1609 (number-to-string m
))
1613 (number-to-string month
))
1615 (number-to-string n
)
1616 (aref icalendar--weekday-array dayname
))
1621 (defun icalendar--convert-date-to-ical (nonmarker entry-main
)
1622 "Convert `diary-date' diary entry to iCalendar format -- unsupported!
1626 NONMARKER is a regular expression matching the start of non-marking
1627 entries. ENTRY-MAIN is the first line of the diary entry."
1628 (if (string-match (concat nonmarker
1629 "%%(diary-date \\([^)]+\\))\\s-*\\(.*?\\) ?$")
1632 (icalendar--dmsg "diary-date %s" entry-main
)
1633 (error "`diary-date' is not supported yet"))
1637 (defun icalendar--convert-cyclic-to-ical (nonmarker entry-main
)
1638 "Convert `diary-cyclic' diary entry to iCalendar format.
1639 NONMARKER is a regular expression matching the start of non-marking
1640 entries. ENTRY-MAIN is the first line of the diary entry."
1641 (if (string-match (concat nonmarker
1642 "%%(diary-cyclic \\([^ ]+\\) +"
1643 "\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\))\\s-*"
1644 "\\(\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
1646 "-\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1648 "\\s-*\\(.*?\\) ?$")
1650 (let* ((frequency (substring entry-main
(match-beginning 1)
1652 (datetime (substring entry-main
(match-beginning 2)
1654 (startisostring (icalendar--datestring-to-isodate
1656 (endisostring (icalendar--datestring-to-isodate
1658 (endisostring+1 (icalendar--datestring-to-isodate
1660 (starttimestring (icalendar--diarytime-to-isotime
1661 (if (match-beginning 4)
1662 (substring entry-main
1666 (if (match-beginning 5)
1667 (substring entry-main
1671 (endtimestring (icalendar--diarytime-to-isotime
1672 (if (match-beginning 7)
1673 (substring entry-main
1677 (if (match-beginning 8)
1678 (substring entry-main
1682 (summary (icalendar--convert-string-for-export
1683 (substring entry-main
(match-beginning 9)
1685 (icalendar--dmsg "diary-cyclic %s" entry-main
)
1686 (when starttimestring
1687 (unless endtimestring
1689 (read (icalendar--rris "^T0?" ""
1691 (setq endtimestring
(format "T%06d"
1693 (list (concat "\nDTSTART;"
1694 (if starttimestring
"VALUE=DATE-TIME:"
1697 (or starttimestring
"")
1699 (if endtimestring
"VALUE=DATE-TIME:"
1701 (if endtimestring endisostring endisostring
+1)
1702 (or endtimestring
"")
1703 "\nRRULE:FREQ=DAILY;INTERVAL=" frequency
1704 ;; strange: korganizer does not expect
1705 ;; BYSOMETHING here...
1711 (defun icalendar--convert-anniversary-to-ical (nonmarker entry-main
)
1712 "Convert `diary-anniversary' diary entry to iCalendar format.
1713 NONMARKER is a regular expression matching the start of non-marking
1714 entries. ENTRY-MAIN is the first line of the diary entry."
1715 (if (string-match (concat nonmarker
1716 "%%(diary-anniversary \\([^)]+\\))\\s-*"
1717 "\\(\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
1719 "-\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1721 "\\s-*\\(.*?\\) ?$")
1723 (let* ((datetime (substring entry-main
(match-beginning 1)
1725 (startisostring (icalendar--datestring-to-isodate
1727 (endisostring (icalendar--datestring-to-isodate
1729 (starttimestring (icalendar--diarytime-to-isotime
1730 (if (match-beginning 3)
1731 (substring entry-main
1735 (if (match-beginning 4)
1736 (substring entry-main
1740 (endtimestring (icalendar--diarytime-to-isotime
1741 (if (match-beginning 6)
1742 (substring entry-main
1746 (if (match-beginning 7)
1747 (substring entry-main
1751 (summary (icalendar--convert-string-for-export
1752 (substring entry-main
(match-beginning 8)
1754 (icalendar--dmsg "diary-anniversary %s" entry-main
)
1755 (when starttimestring
1756 (unless endtimestring
1758 (read (icalendar--rris "^T0?" ""
1760 (setq endtimestring
(format "T%06d"
1762 (list (concat "\nDTSTART;"
1763 (if starttimestring
"VALUE=DATE-TIME:"
1766 (or starttimestring
"")
1768 (if endtimestring
"VALUE=DATE-TIME:"
1771 (or endtimestring
"")
1772 "\nRRULE:FREQ=YEARLY;INTERVAL=1"
1773 ;; the following is redundant,
1774 ;; but korganizer seems to expect this... ;(
1775 ;; and evolution doesn't understand it... :(
1776 ;; so... who is wrong?!
1778 (substring startisostring
4 6)
1780 (substring startisostring
6 8))
1785 ;; ======================================================================
1786 ;; Import -- convert iCalendar to emacs-diary
1787 ;; ======================================================================
1790 (defun icalendar-import-file (ical-filename diary-filename
1791 &optional non-marking
)
1792 "Import an iCalendar file and append to a diary file.
1793 Argument ICAL-FILENAME output iCalendar file.
1794 Argument DIARY-FILENAME input `diary-file'.
1795 Optional argument NON-MARKING determines whether events are created as
1796 non-marking or not."
1797 (interactive "fImport iCalendar data from file: \n\
1800 ;; clean up the diary file
1801 (save-current-buffer
1802 ;; now load and convert from the ical file
1803 (set-buffer (find-file ical-filename
))
1804 (icalendar-import-buffer diary-filename t non-marking
)))
1807 (defun icalendar-import-buffer (&optional diary-file do-not-ask
1809 "Extract iCalendar events from current buffer.
1811 This function searches the current buffer for the first iCalendar
1812 object, reads it and adds all VEVENT elements to the diary
1815 It will ask for each appointment whether to add it to the diary
1816 unless DO-NOT-ASK is non-nil. When called interactively,
1817 DO-NOT-ASK is nil, so that you are asked for each event.
1819 NON-MARKING determines whether diary events are created as
1822 Return code t means that importing worked well, return code nil
1823 means that an error has occurred. Error messages will be in the
1824 buffer `*icalendar-errors*'."
1826 (save-current-buffer
1828 (message "Preparing iCalendar...")
1829 (set-buffer (icalendar--get-unfolded-buffer (current-buffer)))
1830 (goto-char (point-min))
1831 (message "Preparing iCalendar...done")
1832 (if (re-search-forward "^BEGIN:VCALENDAR\\s-*$" nil t
)
1833 (let (ical-contents ical-errors
)
1835 (message "Reading iCalendar...")
1837 (setq ical-contents
(icalendar--read-element nil nil
))
1838 (message "Reading iCalendar...done")
1840 (message "Converting iCalendar...")
1841 (setq ical-errors
(icalendar--convert-ical-to-diary
1843 diary-file do-not-ask non-marking
))
1845 ;; save the diary file if it is visited already
1846 (let ((b (find-buffer-visiting diary-file
)))
1848 (save-current-buffer
1851 (message "Converting iCalendar...done")
1852 ;; return t if no error occurred
1855 "Current buffer does not contain iCalendar contents!")
1856 ;; return nil, i.e. import did not work
1859 (defalias 'icalendar-extract-ical-from-buffer
'icalendar-import-buffer
)
1860 (make-obsolete 'icalendar-extract-ical-from-buffer
'icalendar-import-buffer
"22.1")
1862 (defun icalendar--format-ical-event (event)
1863 "Create a string representation of an iCalendar EVENT."
1864 (if (functionp icalendar-import-format
)
1865 (funcall icalendar-import-format event
)
1866 (let ((string icalendar-import-format
)
1868 '(("%c" CLASS icalendar-import-format-class
)
1869 ("%d" DESCRIPTION icalendar-import-format-description
)
1870 ("%l" LOCATION icalendar-import-format-location
)
1871 ("%o" ORGANIZER icalendar-import-format-organizer
)
1872 ("%s" SUMMARY icalendar-import-format-summary
)
1873 ("%t" STATUS icalendar-import-format-status
)
1874 ("%u" URL icalendar-import-format-url
))))
1875 ;; convert the specifiers in the format string
1877 (let* ((spec (car i
))
1879 (format (car (cddr i
)))
1880 (contents (icalendar--get-event-property event prop
))
1881 (formatted-contents ""))
1882 (when (and contents
(> (length contents
) 0))
1883 (setq formatted-contents
1884 (icalendar--rris "%s"
1885 (icalendar--convert-string-for-import
1887 (symbol-value format
)
1889 (setq string
(icalendar--rris spec
1896 (defun icalendar--convert-ical-to-diary (ical-list diary-file
1897 &optional do-not-ask
1899 "Convert iCalendar data to an Emacs diary file.
1900 Import VEVENTS from the iCalendar object ICAL-LIST and saves them to a
1901 DIARY-FILE. If DO-NOT-ASK is nil the user is asked for each event
1902 whether to actually import it. NON-MARKING determines whether diary
1903 events are created as non-marking.
1904 This function attempts to return t if something goes wrong. In this
1905 case an error string which describes all the errors and problems is
1906 written into the buffer `*icalendar-errors*'."
1907 (let* ((ev (icalendar--all-events ical-list
))
1911 (zone-map (icalendar--convert-all-timezones ical-list
))
1913 ;; step through all events/appointments
1918 (condition-case error-val
1919 (let* ((dtstart (icalendar--get-event-property e
'DTSTART
))
1920 (dtstart-zone (icalendar--find-time-zone
1921 (icalendar--get-event-property-attributes
1924 (dtstart-dec (icalendar--decode-isodatetime dtstart nil
1926 (start-d (icalendar--datetime-to-diary-date
1928 (start-t (icalendar--datetime-to-colontime dtstart-dec
))
1929 (dtend (icalendar--get-event-property e
'DTEND
))
1930 (dtend-zone (icalendar--find-time-zone
1931 (icalendar--get-event-property-attributes
1934 (dtend-dec (icalendar--decode-isodatetime dtend
1936 (dtend-1-dec (icalendar--decode-isodatetime dtend -
1
1941 (summary (icalendar--convert-string-for-import
1942 (or (icalendar--get-event-property e
'SUMMARY
)
1944 (rrule (icalendar--get-event-property e
'RRULE
))
1945 (rdate (icalendar--get-event-property e
'RDATE
))
1946 (duration (icalendar--get-event-property e
'DURATION
)))
1947 (icalendar--dmsg "%s: `%s'" start-d summary
)
1948 ;; check whether start-time is missing
1951 (cadr (icalendar--get-event-property-attributes
1956 (let ((dtend-dec-d (icalendar--add-decoded-times
1958 (icalendar--decode-isoduration duration
)))
1959 (dtend-1-dec-d (icalendar--add-decoded-times
1961 (icalendar--decode-isoduration duration
1963 (if (and dtend-dec
(not (eq dtend-dec dtend-dec-d
)))
1964 (message "Inconsistent endtime and duration for %s"
1966 (setq dtend-dec dtend-dec-d
)
1967 (setq dtend-1-dec dtend-1-dec-d
)))
1968 (setq end-d
(if dtend-dec
1969 (icalendar--datetime-to-diary-date dtend-dec
)
1971 (setq end-1-d
(if dtend-1-dec
1972 (icalendar--datetime-to-diary-date dtend-1-dec
)
1974 (setq end-t
(if (and
1978 (icalendar--get-event-property-attributes
1981 (icalendar--datetime-to-colontime dtend-dec
)
1983 (icalendar--dmsg "start-d: %s, end-d: %s" start-d end-d
)
1988 (icalendar--convert-recurring-to-diary e dtstart-dec start-t
1992 (icalendar--dmsg "rdate event")
1993 (setq diary-string
"")
1994 (mapc (lambda (datestring)
1996 (concat diary-string
1997 (format "......"))))
1998 (icalendar--split-value rdate
)))
1999 ;; non-recurring event
2001 ((not (string= start-d end-d
))
2003 (icalendar--convert-non-recurring-all-day-to-diary
2007 ((and start-t
(or (not end-t
)
2008 (not (string= start-t end-t
))))
2010 (icalendar--convert-non-recurring-not-all-day-to-diary
2011 e dtstart-dec dtend-dec start-t end-t
))
2015 (icalendar--dmsg "all day event")
2016 (setq diary-string
(icalendar--datetime-to-diary-date
2019 ;; add all other elements unless the user doesn't want to have
2024 (concat diary-string
" "
2025 (icalendar--format-ical-event e
)))
2026 (if do-not-ask
(setq summary nil
))
2027 ;; add entry to diary and store actual name of diary
2028 ;; file (in case it was nil)
2030 (icalendar--add-diary-entry diary-string diary-file
2031 non-marking summary
)))
2033 (setq found-error t
)
2035 (format "%s\nCannot handle this event:%s"
2037 ;; FIXME: inform user about ignored event properties
2040 (message "Ignoring event \"%s\"" e
)
2041 (setq found-error t
)
2042 (setq error-string
(format "%s\n%s\nCannot handle this event: %s"
2043 error-val error-string e
))
2044 (message "%s" error-string
))))
2046 ;; insert final newline
2048 (let ((b (find-buffer-visiting diary-file
)))
2050 (save-current-buffer
2052 (goto-char (point-max))
2055 (save-current-buffer
2056 (set-buffer (get-buffer-create "*icalendar-errors*"))
2058 (insert error-string
)))
2059 (message "Converting iCalendar...done")
2062 ;; subroutines for importing
2063 (defun icalendar--convert-recurring-to-diary (e dtstart-dec start-t end-t
)
2064 "Convert recurring iCalendar event E to diary format.
2066 DTSTART-DEC is the DTSTART property of E.
2067 START-T is the event's start time in diary format.
2068 END-T is the event's end time in diary format."
2069 (icalendar--dmsg "recurring event")
2070 (let* ((rrule (icalendar--get-event-property e
'RRULE
))
2071 (rrule-props (icalendar--split-value rrule
))
2072 (frequency (cadr (assoc 'FREQ rrule-props
)))
2073 (until (cadr (assoc 'UNTIL rrule-props
)))
2074 (count (cadr (assoc 'COUNT rrule-props
)))
2075 (interval (read (or (cadr (assoc 'INTERVAL rrule-props
)) "1")))
2076 (dtstart-conv (icalendar--datetime-to-diary-date dtstart-dec
))
2077 (until-conv (icalendar--datetime-to-diary-date
2078 (icalendar--decode-isodatetime until
)))
2079 (until-1-conv (icalendar--datetime-to-diary-date
2080 (icalendar--decode-isodatetime until -
1)))
2083 ;; FIXME FIXME interval!!!!!!!!!!!!!
2087 (message "Must not have UNTIL and COUNT -- ignoring COUNT element!")
2089 (cond ((string-equal frequency
"DAILY")
2090 (setq until
(icalendar--add-decoded-times
2092 (list 0 0 0 (* (read count
) interval
) 0 0)))
2093 (setq until-1
(icalendar--add-decoded-times
2095 (list 0 0 0 (* (- (read count
) 1) interval
)
2098 ((string-equal frequency
"WEEKLY")
2099 (setq until
(icalendar--add-decoded-times
2101 (list 0 0 0 (* (read count
) 7 interval
) 0 0)))
2102 (setq until-1
(icalendar--add-decoded-times
2104 (list 0 0 0 (* (- (read count
) 1) 7
2107 ((string-equal frequency
"MONTHLY")
2108 (setq until
(icalendar--add-decoded-times
2109 dtstart-dec
(list 0 0 0 0 (* (- (read count
) 1)
2111 (setq until-1
(icalendar--add-decoded-times
2112 dtstart-dec
(list 0 0 0 0 (* (- (read count
) 1)
2115 ((string-equal frequency
"YEARLY")
2116 (setq until
(icalendar--add-decoded-times
2117 dtstart-dec
(list 0 0 0 0 0 (* (- (read count
) 1)
2119 (setq until-1
(icalendar--add-decoded-times
2121 (list 0 0 0 0 0 (* (- (read count
) 1)
2125 (message "Cannot handle COUNT attribute for `%s' events."
2127 (setq until-conv
(icalendar--datetime-to-diary-date until
))
2128 (setq until-1-conv
(icalendar--datetime-to-diary-date until-1
))
2131 (cond ((string-equal frequency
"WEEKLY")
2132 (let* ((byday (cadr (assoc 'BYDAY rrule-props
)))
2134 (icalendar--get-weekday-numbers byday
))
2136 (when (> (length weekdays
) 1)
2137 (format "(memq (calendar-day-of-week date) '%s) "
2141 ;; weekly and all-day
2142 (icalendar--dmsg "weekly all-day")
2148 "(diary-block %s %s))")
2150 (format "(diary-cyclic %d %s) "
2154 (if count until-1-conv until-conv
)
2157 (format "%%%%(and %s(diary-cyclic %d %s))"
2158 (or weekday-clause
"")
2159 (if weekday-clause
1 (* interval
7))
2161 ;; weekly and not all-day
2162 (icalendar--dmsg "weekly not-all-day")
2168 "(diary-block %s %s)) "
2171 (format "(diary-cyclic %d %s) "
2177 (if end-t
"-" "") (or end-t
"")))
2180 ;; DTSTART;VALUE=DATE-TIME:20030919T090000
2181 ;; DTEND;VALUE=DATE-TIME:20030919T113000
2184 "%%%%(and %s(diary-cyclic %d %s)) %s%s%s"
2185 (or weekday-clause
"")
2186 (if weekday-clause
1 (* interval
7))
2189 (if end-t
"-" "") (or end-t
"")))))))
2191 ((string-equal frequency
"YEARLY")
2192 (icalendar--dmsg "yearly")
2194 (let ((day (nth 3 dtstart-dec
))
2195 (month (nth 4 dtstart-dec
)))
2196 (setq result
(concat "%%(and (diary-date "
2197 (cond ((eq (icalendar--date-style) 'iso
)
2198 (format "t %d %d" month day
))
2199 ((eq (icalendar--date-style) 'european
)
2200 (format "%d %d t" day month
))
2201 ((eq (icalendar--date-style) 'american
)
2202 (format "%d %d t" month day
)))
2211 (setq result
(format
2212 "%%%%(and (diary-anniversary %s)) %s%s%s"
2215 (if end-t
"-" "") (or end-t
"")))))
2217 ((string-equal frequency
"MONTHLY")
2218 (icalendar--dmsg "monthly")
2221 "%%%%(and (diary-date %s) (diary-block %s %s)) %s%s%s"
2222 (let ((day (nth 3 dtstart-dec
)))
2223 (cond ((eq (icalendar--date-style) 'iso
)
2224 (format "t t %d" day
))
2225 ((eq (icalendar--date-style) 'european
)
2226 (format "%d t t" day
))
2227 ((eq (icalendar--date-style) 'american
)
2228 (format "t %d t" day
))))
2232 (if (eq (icalendar--date-style) 'iso
) "9999 1 1" "1 1 9999")) ;; FIXME: should be unlimited
2234 (if end-t
"-" "") (or end-t
""))))
2236 ((and (string-equal frequency
"DAILY"))
2240 (concat "%%%%(and (diary-cyclic %s %s) "
2241 "(diary-block %s %s)) %s%s%s")
2242 interval dtstart-conv dtstart-conv
2243 (if count until-1-conv until-conv
)
2245 (if end-t
"-" "") (or end-t
"")))
2248 "%%%%(and (diary-cyclic %s %s)) %s%s%s"
2252 (if end-t
"-" "") (or end-t
""))))))
2253 ;; Handle exceptions from recurrence rules
2254 (let ((ex-dates (icalendar--get-event-properties e
'EXDATE
)))
2256 (let* ((ex-start (icalendar--decode-isodatetime
2258 (ex-d (icalendar--datetime-to-diary-date
2261 (icalendar--rris "^%%(\\(and \\)?"
2263 "%%%%(and (not (diary-date %s)) "
2266 (setq ex-dates
(cdr ex-dates
))))
2267 ;; FIXME: exception rules are not recognized
2268 (if (icalendar--get-event-property e
'EXRULE
)
2271 "\n Exception rules: "
2272 (icalendar--get-event-properties
2276 (defun icalendar--convert-non-recurring-all-day-to-diary (event start-d end-d
)
2277 "Convert non-recurring iCalendar EVENT to diary format.
2279 DTSTART is the decoded DTSTART property of E.
2280 Argument START-D gives the first day.
2281 Argument END-D gives the last day."
2282 (icalendar--dmsg "non-recurring all-day event")
2283 (format "%%%%(and (diary-block %s %s))" start-d end-d
))
2285 (defun icalendar--convert-non-recurring-not-all-day-to-diary (event dtstart-dec
2289 "Convert recurring icalendar EVENT to diary format.
2291 DTSTART-DEC is the decoded DTSTART property of E.
2292 DTEND-DEC is the decoded DTEND property of E.
2293 START-T is the event's start time in diary format.
2294 END-T is the event's end time in diary format."
2295 (icalendar--dmsg "not all day event")
2298 (icalendar--datetime-to-diary-date
2303 (icalendar--datetime-to-diary-date
2307 (defun icalendar--add-diary-entry (string diary-file non-marking
2309 "Add STRING to the diary file DIARY-FILE.
2310 STRING must be a properly formatted valid diary entry. NON-MARKING
2311 determines whether diary events are created as non-marking. If
2312 SUMMARY is not nil it must be a string that gives the summary of the
2313 entry. In this case the user will be asked whether he wants to insert
2315 (when (or (not summary
)
2316 (y-or-n-p (format "Add appointment for `%s' to diary? "
2320 (y-or-n-p (format "Make appointment non-marking? "))))
2321 (save-window-excursion
2324 (read-file-name "Add appointment to this diary file: ")))
2325 ;; Note: diary-make-entry will add a trailing blank char.... :(
2326 (funcall (if (fboundp 'diary-make-entry
)
2329 string non-marking diary-file
)))
2330 ;; Würgaround to remove the trailing blank char
2331 (with-current-buffer (find-file diary-file
)
2332 (goto-char (point-max))
2333 (if (= (char-before) ?
)
2335 ;; return diary-file in case it has been changed interactively
2338 ;; ======================================================================
2340 ;; ======================================================================
2341 (defun icalendar-import-format-sample (event)
2342 "Example function for formatting an iCalendar EVENT."
2343 (format (concat "SUMMARY=`%s' DESCRIPTION=`%s' LOCATION=`%s' ORGANIZER=`%s' "
2344 "STATUS=`%s' URL=`%s' CLASS=`%s'")
2345 (or (icalendar--get-event-property event
'SUMMARY
) "")
2346 (or (icalendar--get-event-property event
'DESCRIPTION
) "")
2347 (or (icalendar--get-event-property event
'LOCATION
) "")
2348 (or (icalendar--get-event-property event
'ORGANIZER
) "")
2349 (or (icalendar--get-event-property event
'STATUS
) "")
2350 (or (icalendar--get-event-property event
'URL
) "")
2351 (or (icalendar--get-event-property event
'CLASS
) "")))
2353 (provide 'icalendar
)
2355 ;;; icalendar.el ends here