1 ;;; icalendar.el --- iCalendar implementation -*-coding: utf-8 -*-
3 ;; Copyright (C) 2002, 2003, 2004, 2005 Free Software Foundation, Inc.
5 ;; Author: Ulf Jasper <ulf.jasper@web.de>
6 ;; Created: August 2002
8 ;; Human-Keywords: calendar, diary, iCalendar, vCalendar
10 ;; This file is part of GNU Emacs.
12 ;; GNU Emacs is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation; either version 2, or (at your option)
17 ;; GNU Emacs is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;; GNU General Public License for more details.
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs; see the file COPYING. If not, write to the
24 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
25 ;; Boston, MA 02110-1301, USA.
29 ;; This package is documented in the Emacs Manual.
32 ;; - Diary entries which have a start time but no end time are assumed to
33 ;; last for one hour when they are exported.
34 ;; - Weekly diary entries are assumed to occur the first time in the first
35 ;; week of the year 2000 when they are exported.
36 ;; - Yearly diary entries are assumed to occur the first time in the year
37 ;; 1900 when they are exported.
41 ;; 0.07 onwards: see lisp/ChangeLog
43 ;; 0.06: Bugfixes regarding icalendar-import-format-*.
44 ;; Fix in icalendar-convert-diary-to-ical -- thanks to Philipp
47 ;; 0.05: New import format scheme: Replaced icalendar-import-prefix-*,
48 ;; icalendar-import-ignored-properties, and
49 ;; icalendar-import-separator with icalendar-import-format(-*).
50 ;; icalendar-import-file and icalendar-convert-diary-to-ical
51 ;; have an extra parameter which should prevent them from
52 ;; erasing their target files (untested!).
53 ;; Tested with Emacs 21.3.2
55 ;; 0.04: Bugfix: import: double quoted param values did not work
56 ;; Read DURATION property when importing.
57 ;; Added parameter icalendar-duration-correction.
59 ;; 0.03: Export takes care of european-calendar-style.
60 ;; Tested with Emacs 21.3.2 and XEmacs 21.4.12
62 ;; 0.02: Should work in XEmacs now. Thanks to Len Trigg for the
64 ;; Added exporting from Emacs diary to ical.
65 ;; Some bugfixes, after testing with calendars from
66 ;; http://icalshare.com.
67 ;; Tested with Emacs 21.3.2 and XEmacs 21.4.12
69 ;; 0.01: First published version. Trial version. Alpha version.
71 ;; ======================================================================
74 ;; * Import from ical to diary:
75 ;; + Need more properties for icalendar-import-format
76 ;; + check vcalendar version
77 ;; + check (unknown) elements
78 ;; + recurring events!
79 ;; + works for european style calendars only! Does it?
81 ;; + exceptions in recurring events
82 ;; + the parser is too soft
83 ;; + error log is incomplete
84 ;; + nice to have: #include "webcal://foo.com/some-calendar.ics"
85 ;; + timezones, currently all times are local!
87 ;; * Export from diary to ical
88 ;; + diary-date, diary-float, and self-made sexp entries are not
92 ;; + clean up all those date/time parsing functions
93 ;; + Handle todo items?
94 ;; + Check iso 8601 for datetime and period
95 ;; + Which chars to (un)escape?
100 (defconst icalendar-version
0.12
101 "Version number of icalendar.el.")
103 ;; ======================================================================
105 ;; ======================================================================
106 (defgroup icalendar nil
111 (defcustom icalendar-import-format
113 "Format string for importing events from iCalendar into Emacs diary.
114 This string defines how iCalendar events are inserted into diary
115 file. Meaning of the specifiers:
116 %d Description, see `icalendar-import-format-description'
117 %l Location, see `icalendar-import-format-location'
118 %o Organizer, see `icalendar-import-format-organizer'
119 %s Subject, see `icalendar-import-format-subject'"
123 (defcustom icalendar-import-format-subject
125 "Format string defining how the subject element is formatted.
126 This applies only if the subject is not empty! `%s' is replaced
131 (defcustom icalendar-import-format-description
133 "Format string defining how the description element is formatted.
134 This applies only if the description is not empty! `%s' is
135 replaced by the description."
139 (defcustom icalendar-import-format-location
141 "Format string defining how the location element is formatted.
142 This applies only if the location is not empty! `%s' is replaced
147 (defcustom icalendar-import-format-organizer
149 "Format string defining how the organizer element is formatted.
150 This applies only if the organizer is not empty! `%s' is
151 replaced by the organizer."
155 (defvar icalendar-debug nil
156 "Enable icalendar debug messages.")
158 ;; ======================================================================
159 ;; NO USER SERVICABLE PARTS BELOW THIS LINE
160 ;; ======================================================================
162 (defconst icalendar--weekday-array
["SU" "MO" "TU" "WE" "TH" "FR" "SA"])
164 ;; ======================================================================
165 ;; all the other libs we need
166 ;; ======================================================================
169 ;; ======================================================================
171 ;; ======================================================================
172 (defun icalendar--dmsg (&rest args
)
173 "Print message ARGS if `icalendar-debug' is non-nil."
175 (apply 'message args
)))
177 ;; ======================================================================
178 ;; Core functionality
179 ;; Functions for parsing icalendars, importing and so on
180 ;; ======================================================================
182 (defun icalendar--get-unfolded-buffer (folded-ical-buffer)
183 "Return a new buffer containing the unfolded contents of a buffer.
184 Folding is the iCalendar way of wrapping long lines. In the
185 created buffer all occurrences of CR LF BLANK are replaced by the
186 empty string. Argument FOLDED-ICAL-BUFFER is the unfolded input
188 (let ((unfolded-buffer (get-buffer-create " *icalendar-work*")))
190 (set-buffer unfolded-buffer
)
192 (insert-buffer-substring folded-ical-buffer
)
193 (goto-char (point-min))
194 (while (re-search-forward "\r?\n[ \t]" nil t
)
195 (replace-match "" nil nil
)))
198 (defsubst icalendar--rris
(re rp st
)
199 "Replace regexp RE with RP in string ST and return the new string.
200 This is here for compatibility with XEmacs."
202 (if (fboundp 'replace-in-string
)
203 (save-match-data ;; apparently XEmacs needs save-match-data
204 (replace-in-string st re rp
))
206 (replace-regexp-in-string re rp st
)))
208 (defun icalendar--read-element (invalue inparams
)
209 "Recursively read the next iCalendar element in the current buffer.
210 INVALUE gives the current iCalendar element we are reading.
211 INPARAMS gives the current parameters.....
212 This function calls itself recursively for each nested calendar element
214 (let (element children line name params param param-name param-value
219 (re-search-forward "^\\([A-Za-z0-9-]+\\)[;:]" nil t
))
220 (setq name
(intern (match-string 1)))
224 (while (looking-at ";")
225 (re-search-forward ";\\([A-Za-z0-9-]+\\)=" nil nil
)
226 (setq param-name
(intern (match-string 1)))
227 (re-search-forward "\\(\\([^;,:\"]+\\)\\|\"\\([^\"]+\\)\"\\)[;:]"
230 (setq param-value
(or (match-string 2) (match-string 3)))
231 (setq param
(list param-name param-value
))
232 (while (looking-at ",")
233 (re-search-forward "\\(\\([^;,:]+\\)\\|\"\\([^\"]+\\)\"\\)"
236 (setq param-value
(match-string 2))
237 (setq param-value
(match-string 3)))
238 (setq param
(append param param-value
)))
239 (setq params
(append params param
)))
240 (unless (looking-at ":")
243 (re-search-forward "\\(.*\\)\\(\r?\n[ \t].*\\)*" nil t
)
244 (setq value
(icalendar--rris "\r?\n[ \t]" "" (match-string 0)))
245 (setq line
(list name params value
))
246 (cond ((eq name
'BEGIN
)
249 (list (icalendar--read-element (intern value
)
254 (setq element
(append element
(list line
))))))
256 (list invalue inparams element children
)
259 ;; ======================================================================
260 ;; helper functions for examining events
261 ;; ======================================================================
263 ;;(defsubst icalendar--get-all-event-properties (event)
264 ;; "Return the list of properties in this EVENT."
265 ;; (car (cddr event)))
267 (defun icalendar--get-event-property (event prop
)
268 "For the given EVENT return the value of the first occurence of PROP."
270 (let ((props (car (cddr event
))) pp
)
272 (setq pp
(car props
))
273 (if (eq (car pp
) prop
)
274 (throw 'found
(car (cddr pp
))))
275 (setq props
(cdr props
))))
278 (defun icalendar--get-event-property-attributes (event prop
)
279 "For the given EVENT return attributes of the first occurence of PROP."
281 (let ((props (car (cddr event
))) pp
)
283 (setq pp
(car props
))
284 (if (eq (car pp
) prop
)
285 (throw 'found
(cadr pp
)))
286 (setq props
(cdr props
))))
289 (defun icalendar--get-event-properties (event prop
)
290 "For the given EVENT return a list of all values of the property PROP."
291 (let ((props (car (cddr event
))) pp result
)
293 (setq pp
(car props
))
294 (if (eq (car pp
) prop
)
295 (setq result
(append (split-string (car (cddr pp
)) ",") result
)))
296 (setq props
(cdr props
)))
299 ;; (defun icalendar--set-event-property (event prop new-value)
300 ;; "For the given EVENT set the property PROP to the value NEW-VALUE."
302 ;; (let ((props (car (cddr event))) pp)
304 ;; (setq pp (car props))
305 ;; (when (eq (car pp) prop)
306 ;; (setcdr (cdr pp) new-value)
307 ;; (throw 'found (car (cddr pp))))
308 ;; (setq props (cdr props)))
309 ;; (setq props (car (cddr event)))
310 ;; (setcar (cddr event)
311 ;; (append props (list (list prop nil new-value)))))))
313 (defun icalendar--get-children (node name
)
314 "Return all children of the given NODE which have a name NAME.
315 For instance the VCALENDAR node can have VEVENT children as well as VTODO
318 (children (cadr (cddr node
))))
319 (when (eq (car node
) name
)
321 ;;(message "%s" node)
326 (icalendar--get-children n name
))
330 (setq result
(append result subresult
))
331 (setq result subresult
)))))
335 (defun icalendar--all-events (icalendar)
336 "Return the list of all existing events in the given ICALENDAR."
337 (icalendar--get-children (car icalendar
) 'VEVENT
))
339 (defun icalendar--split-value (value-string)
340 "Split VALUE-STRING at ';='."
342 param-name param-value
)
345 (set-buffer (get-buffer-create " *icalendar-work*"))
346 (set-buffer-modified-p nil
)
348 (insert value-string
)
349 (goto-char (point-min))
352 "\\([A-Za-z0-9-]+\\)=\\(\\([^;,:]+\\)\\|\"\\([^\"]+\\)\"\\);?"
354 (setq param-name
(intern (match-string 1)))
355 (setq param-value
(match-string 2))
357 (append result
(list (list param-name param-value
)))))))
360 (defun icalendar--decode-isodatetime (isodatetimestring &optional day-shift
)
361 "Return ISODATETIMESTRING in format like `decode-time'.
362 Converts from ISO-8601 to Emacs representation. If
363 ISODATETIMESTRING specifies UTC time (trailing letter Z) the
364 decoded time is given in the local time zone! If optional
365 parameter DAY-SHIFT is non-nil the result is shifted by DAY-SHIFT
368 FIXME: TZID-attributes are ignored....!
369 FIXME: multiple comma-separated values should be allowed!"
370 (icalendar--dmsg isodatetimestring
)
371 (if isodatetimestring
372 ;; day/month/year must be present
373 (let ((year (read (substring isodatetimestring
0 4)))
374 (month (read (substring isodatetimestring
4 6)))
375 (day (read (substring isodatetimestring
6 8)))
379 (when (> (length isodatetimestring
) 12)
380 ;; hour/minute present
381 (setq hour
(read (substring isodatetimestring
9 11)))
382 (setq minute
(read (substring isodatetimestring
11 13))))
383 (when (> (length isodatetimestring
) 14)
385 (setq second
(read (substring isodatetimestring
13 15))))
386 (when (and (> (length isodatetimestring
) 15)
387 ;; UTC specifier present
388 (char-equal ?Z
(aref isodatetimestring
15)))
389 ;; if not UTC add current-time-zone offset
390 (setq second
(+ (car (current-time-zone)) second
)))
391 ;; shift if necessary
393 (let ((mdy (calendar-gregorian-from-absolute
394 (+ (calendar-absolute-from-gregorian
395 (list month day year
))
397 (setq month
(nth 0 mdy
))
398 (setq day
(nth 1 mdy
))
399 (setq year
(nth 2 mdy
))))
400 ;; create the decoded date-time
403 (decode-time (encode-time second minute hour day month year
))
405 (message "Cannot decode \"%s\"" isodatetimestring
)
406 ;; hope for the best...
407 (list second minute hour day month year
0 nil
0))))
408 ;; isodatetimestring == nil
411 (defun icalendar--decode-isoduration (isodurationstring
412 &optional duration-correction
)
413 "Convert ISODURATIONSTRING into format provided by `decode-time'.
414 Converts from ISO-8601 to Emacs representation. If ISODURATIONSTRING
415 specifies UTC time (trailing letter Z) the decoded time is given in
418 Optional argument DURATION-CORRECTION shortens result by one day.
420 FIXME: TZID-attributes are ignored....!
421 FIXME: multiple comma-separated values should be allowed!"
422 (if isodurationstring
427 "\\(\\([0-9]+\\)D\\)" ; days only
429 "\\(\\(\\([0-9]+\\)D\\)?T\\(\\([0-9]+\\)H\\)?" ; opt days
430 "\\(\\([0-9]+\\)M\\)?\\(\\([0-9]+\\)S\\)?\\)" ; mand. time
432 "\\(\\([0-9]+\\)W\\)" ; weeks only
433 "\\)$") isodurationstring
)
441 ((match-beginning 2) ;days only
442 (setq days
(read (substring isodurationstring
445 (when duration-correction
446 (setq days
(1- days
))))
447 ((match-beginning 4) ;days and time
448 (if (match-beginning 5)
449 (setq days
(* 7 (read (substring isodurationstring
452 (if (match-beginning 7)
453 (setq hours
(read (substring isodurationstring
456 (if (match-beginning 9)
457 (setq minutes
(read (substring isodurationstring
460 (if (match-beginning 11)
461 (setq seconds
(read (substring isodurationstring
464 ((match-beginning 13) ;weeks only
465 (setq days
(* 7 (read (substring isodurationstring
468 (list seconds minutes hours days months years
)))
469 ;; isodatetimestring == nil
472 (defun icalendar--add-decoded-times (time1 time2
)
474 Both times must be given in decoded form. One of these times must be
475 valid (year > 1900 or something)."
476 ;; FIXME: does this function exist already?
477 (decode-time (encode-time
478 (+ (nth 0 time1
) (nth 0 time2
))
479 (+ (nth 1 time1
) (nth 1 time2
))
480 (+ (nth 2 time1
) (nth 2 time2
))
481 (+ (nth 3 time1
) (nth 3 time2
))
482 (+ (nth 4 time1
) (nth 4 time2
))
483 (+ (nth 5 time1
) (nth 5 time2
))
486 ;;(or (nth 6 time1) (nth 6 time2)) ;; FIXME?
489 (defun icalendar--datetime-to-noneuropean-date (datetime &optional separator
)
490 "Convert the decoded DATETIME to non-european-style format.
491 Optional argument SEPARATOR gives the separator between month,
492 day, and year. If nil a blank character is used as separator.
493 Non-European format: \"month day year\"."
495 (format "%d%s%d%s%d" (nth 4 datetime
) ;month
497 (nth 3 datetime
) ;day
499 (nth 5 datetime
)) ;year
503 (defun icalendar--datetime-to-european-date (datetime &optional separator
)
504 "Convert the decoded DATETIME to European format.
505 Optional argument SEPARATOR gives the separator between month,
506 day, and year. If nil a blank character is used as separator.
507 European format: (day month year).
510 (format "%d%s%d%s%d" (nth 3 datetime
) ;day
512 (nth 4 datetime
) ;month
514 (nth 5 datetime
)) ;year
518 (defun icalendar--datetime-to-diary-date (datetime &optional separator
)
519 "Convert the decoded DATETIME to diary format.
520 Optional argument SEPARATOR gives the separator between month,
521 day, and year. If nil a blank character is used as separator.
522 Call icalendar--datetime-to-(non)-european-date according to
523 value of `european-calendar-style'."
524 (if european-calendar-style
525 (icalendar--datetime-to-european-date datetime separator
)
526 (icalendar--datetime-to-noneuropean-date datetime separator
)))
528 (defun icalendar--datetime-to-colontime (datetime)
529 "Extract the time part of a decoded DATETIME into 24-hour format.
530 Note that this silently ignores seconds."
531 (format "%02d:%02d" (nth 2 datetime
) (nth 1 datetime
)))
533 (defun icalendar--get-month-number (monthname)
534 "Return the month number for the given MONTHNAME."
537 (m (downcase monthname
)))
538 (mapc (lambda (month)
539 (let ((mm (downcase month
)))
540 (if (or (string-equal mm m
)
541 (string-equal (substring mm
0 3) m
))
543 (setq num
(1+ num
))))
544 calendar-month-name-array
))
548 (defun icalendar--get-weekday-number (abbrevweekday)
549 "Return the number for the ABBREVWEEKDAY."
553 (aw (downcase abbrevweekday
)))
555 (let ((d (downcase day
)))
556 (if (string-equal d aw
)
558 (setq num
(1+ num
))))
559 icalendar--weekday-array
)))
563 (defun icalendar--get-weekday-abbrev (weekday)
564 "Return the abbreviated WEEKDAY."
567 (w (downcase weekday
)))
569 (let ((d (downcase day
)))
570 (if (or (string-equal d w
)
571 (string-equal (substring d
0 3) w
))
572 (throw 'found
(aref icalendar--weekday-array num
)))
573 (setq num
(1+ num
))))
574 calendar-day-name-array
))
578 (defun icalendar--date-to-isodate (date &optional day-shift
)
579 "Convert DATE to iso-style date.
580 DATE must be a list of the form (month day year).
581 If DAY-SHIFT is non-nil, the result is shifted by DAY-SHIFT days."
582 (let ((mdy (calendar-gregorian-from-absolute
583 (+ (calendar-absolute-from-gregorian date
)
585 (format "%04d%02d%02d" (nth 2 mdy
) (nth 0 mdy
) (nth 1 mdy
))))
588 (defun icalendar--datestring-to-isodate (datestring &optional day-shift
)
589 "Convert diary-style DATESTRING to iso-style date.
590 If DAY-SHIFT is non-nil, the result is shifted by DAY-SHIFT days
591 -- DAY-SHIFT must be either nil or an integer. This function
592 takes care of european-style."
593 (let ((day -
1) month year
)
595 (cond ( ;; numeric date
596 (string-match (concat "\\s-*"
597 "0?\\([1-9][0-9]?\\)[ \t/]\\s-*"
598 "0?\\([1-9][0-9]?\\),?[ \t/]\\s-*"
599 "\\([0-9]\\{4\\}\\)")
601 (setq day
(read (substring datestring
(match-beginning 1)
603 (setq month
(read (substring datestring
(match-beginning 2)
605 (setq year
(read (substring datestring
(match-beginning 3)
607 (unless european-calendar-style
611 ( ;; date contains month names -- european-style
612 (and european-calendar-style
613 (string-match (concat "\\s-*"
614 "0?\\([123]?[0-9]\\)[ \t/]\\s-*"
615 "\\([A-Za-z][^ ]+\\)[ \t/]\\s-*"
616 "\\([0-9]\\{4\\}\\)")
618 (setq day
(read (substring datestring
(match-beginning 1)
620 (setq month
(icalendar--get-month-number
621 (substring datestring
(match-beginning 2)
623 (setq year
(read (substring datestring
(match-beginning 3)
625 ( ;; date contains month names -- non-european-style
626 (and (not european-calendar-style
)
627 (string-match (concat "\\s-*"
628 "\\([A-Za-z][^ ]+\\)[ \t/]\\s-*"
629 "0?\\([123]?[0-9]\\),?[ \t/]\\s-*"
630 "\\([0-9]\\{4\\}\\)")
632 (setq day
(read (substring datestring
(match-beginning 2)
634 (setq month
(icalendar--get-month-number
635 (substring datestring
(match-beginning 1)
637 (setq year
(read (substring datestring
(match-beginning 3)
642 (let ((mdy (calendar-gregorian-from-absolute
643 (+ (calendar-absolute-from-gregorian (list month day
646 (format "%04d%02d%02d" (nth 2 mdy
) (nth 0 mdy
) (nth 1 mdy
)))
649 (defun icalendar--diarytime-to-isotime (timestring ampmstring
)
650 "Convert a a time like 9:30pm to an iso-conform string like T213000.
651 In this example the TIMESTRING would be \"9:30\" and the AMPMSTRING
654 (let ((starttimenum (read (icalendar--rris ":" "" timestring
))))
655 ;; take care of am/pm style
656 (if (and ampmstring
(string= "pm" ampmstring
))
657 (setq starttimenum
(+ starttimenum
1200)))
658 (format "T%04d00" starttimenum
))
661 (defun icalendar--convert-string-for-export (string)
662 "Escape comma and other critical characters in STRING."
663 (icalendar--rris "," "\\\\," string
))
665 (defun icalendar--convert-string-for-import (string)
666 "Remove escape chars for comma, semicolon etc. from STRING."
668 "\\\\n" "\n " (icalendar--rris
669 "\\\\\"" "\"" (icalendar--rris
670 "\\\\;" ";" (icalendar--rris
671 "\\\\," "," string
)))))
673 ;; ======================================================================
674 ;; Export -- convert emacs-diary to icalendar
675 ;; ======================================================================
678 (defun icalendar-export-file (diary-filename ical-filename
)
679 "Export diary file to iCalendar format.
680 All diary entries in the file DIARY-FILENAME are converted to iCalendar
681 format. The result is appended to the file ICAL-FILENAME."
682 (interactive "FExport diary data from file:
683 Finto iCalendar file: ")
685 (set-buffer (find-file diary-filename
))
686 (icalendar-export-region (point-min) (point-max) ical-filename
)))
688 (defalias 'icalendar-convert-diary-to-ical
'icalendar-export-file
)
689 (make-obsolete 'icalendar-convert-diary-to-ical
'icalendar-export-file
)
692 (defun icalendar-export-region (min max ical-filename
)
693 "Export region in diary file to iCalendar format.
694 All diary entries in the region from MIN to MAX in the current buffer are
695 converted to iCalendar format. The result is appended to the file
697 This function attempts to return t if something goes wrong. In this
698 case an error string which describes all the errors and problems is
699 written into the buffer `*icalendar-errors*'."
701 FExport diary data into iCalendar file: ")
709 (nonmarker (concat "^" (regexp-quote diary-nonmarking-symbol
)
711 ;; prepare buffer with error messages
713 (set-buffer (get-buffer-create "*icalendar-errors*"))
719 (while (re-search-forward
720 "^\\([^ \t\n].+\\)\\(\\(\n[ \t].*\\)*\\)" max t
)
721 (setq entry-main
(match-string 1))
722 (if (match-beginning 2)
723 (setq entry-rest
(match-string 2))
724 (setq entry-rest
""))
725 (setq header
(format "\nBEGIN:VEVENT\nUID:emacs%d%d%d"
727 (cadr (current-time))
728 (car (cddr (current-time)))))
729 (condition-case error-val
733 ;; anniversaries -- %%(diary-anniversary ...)
734 (icalendar--convert-anniversary-to-ical nonmarker
736 ;; cyclic events -- %%(diary-cyclic ...)
737 (icalendar--convert-cyclic-to-ical nonmarker entry-main
)
738 ;; diary-date -- %%(diary-date ...)
739 (icalendar--convert-date-to-ical nonmarker entry-main
)
740 ;; float events -- %%(diary-float ...)
741 (icalendar--convert-float-to-ical nonmarker entry-main
)
742 ;; block events -- %%(diary-block ...)
743 (icalendar--convert-block-to-ical nonmarker entry-main
)
744 ;; other sexp diary entries
745 (icalendar--convert-sexp-to-ical nonmarker entry-main
)
746 ;; weekly by day -- Monday 8:30 Team meeting
747 (icalendar--convert-weekly-to-ical nonmarker entry-main
)
748 ;; yearly by day -- 1 May Tag der Arbeit
749 (icalendar--convert-yearly-to-ical nonmarker entry-main
)
750 ;; "ordinary" events, start and end time given
752 (icalendar--convert-ordinary-to-ical nonmarker entry-main
)
754 ;; Oops! what's that?
755 (error "Could not parse entry")))
756 (unless (string= entry-rest
"")
758 (concat contents
"\nDESCRIPTION:"
759 (icalendar--convert-string-for-export
761 (setq result
(concat result header contents
"\nEND:VEVENT")))
766 (set-buffer (get-buffer-create "*icalendar-errors*"))
767 (insert (format "Error in line %d -- %s: `%s'\n"
768 (count-lines (point-min) (point))
772 ;; we're done, insert everything into the file
774 (let ((coding-system-for-write 'utf-8
))
775 (set-buffer (find-file ical-filename
))
776 (goto-char (point-max))
777 (insert "BEGIN:VCALENDAR")
778 (insert "\nPRODID:-//Emacs//NONSGML icalendar.el//EN")
779 (insert "\nVERSION:2.0")
781 (insert "\nEND:VCALENDAR\n")
782 ;; save the diary file
787 (defun icalendar--convert-ordinary-to-ical (nonmarker entry-main
)
788 "Convert \"ordinary\" diary entry to icalendar format.
790 NONMARKER is a regular expression matching the start of non-marking
791 entries. ENTRY-MAIN is the first line of the diary entry."
792 (if (string-match (concat nonmarker
793 "\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\)\\s-*"
794 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
796 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
800 (let* ((datetime (substring entry-main
(match-beginning 1)
802 (startisostring (icalendar--datestring-to-isodate
804 (endisostring (icalendar--datestring-to-isodate
806 (starttimestring (icalendar--diarytime-to-isotime
807 (if (match-beginning 3)
808 (substring entry-main
812 (if (match-beginning 4)
813 (substring entry-main
817 (endtimestring (icalendar--diarytime-to-isotime
818 (if (match-beginning 6)
819 (substring entry-main
823 (if (match-beginning 7)
824 (substring entry-main
828 (summary (icalendar--convert-string-for-export
829 (substring entry-main
(match-beginning 8)
831 (icalendar--dmsg "ordinary %s" entry-main
)
833 (unless startisostring
834 (error "Could not parse date"))
835 (when starttimestring
836 (unless endtimestring
838 (read (icalendar--rris "^T0?" ""
840 (setq endtimestring
(format "T%06d"
843 (if starttimestring
"VALUE=DATE-TIME:"
846 (or starttimestring
"")
848 (if endtimestring
"VALUE=DATE-TIME:"
853 (or endtimestring
"")
859 (defun icalendar--convert-weekly-to-ical (nonmarker entry-main
)
860 "Convert weekly diary entry to icalendar format.
862 NONMARKER is a regular expression matching the start of non-marking
863 entries. ENTRY-MAIN is the first line of the diary entry."
864 (if (and (string-match (concat nonmarker
866 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)"
869 "\\([1-9][0-9]?:[0-9][0-9]\\)"
874 (icalendar--get-weekday-abbrev
875 (substring entry-main
(match-beginning 1)
877 (let* ((day (icalendar--get-weekday-abbrev
878 (substring entry-main
(match-beginning 1)
880 (starttimestring (icalendar--diarytime-to-isotime
881 (if (match-beginning 3)
882 (substring entry-main
886 (if (match-beginning 4)
887 (substring entry-main
891 (endtimestring (icalendar--diarytime-to-isotime
892 (if (match-beginning 6)
893 (substring entry-main
897 (if (match-beginning 7)
898 (substring entry-main
902 (summary (icalendar--convert-string-for-export
903 (substring entry-main
(match-beginning 8)
905 (icalendar--dmsg "weekly %s" entry-main
)
907 (when starttimestring
908 (unless endtimestring
910 (icalendar--rris "^T0?" ""
912 (setq endtimestring
(format "T%06d"
918 ;; find the correct week day,
919 ;; 1st january 2000 was a saturday
922 (+ (icalendar--get-weekday-number day
) 2))
923 (or starttimestring
"")
930 ;; end is non-inclusive!
931 (+ (icalendar--get-weekday-number day
)
932 (if endtimestring
2 3)))
933 (or endtimestring
"")
935 "\nRRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY="
940 (defun icalendar--convert-yearly-to-ical (nonmarker entry-main
)
941 "Convert yearly diary entry to icalendar format.
943 NONMARKER is a regular expression matching the start of non-marking
944 entries. ENTRY-MAIN is the first line of the diary entry."
945 (if (string-match (concat nonmarker
946 (if european-calendar-style
947 "0?\\([1-9]+[0-9]?\\)\\s-+\\([a-z]+\\)\\s-+"
948 "\\([a-z]+\\)\\s-+0?\\([1-9]+[0-9]?\\)\\s-+")
950 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
952 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
954 "\\s-*\\([^0-9]+.*\\)$" ; must not match years
957 (let* ((daypos (if european-calendar-style
1 2))
958 (monpos (if european-calendar-style
2 1))
959 (day (read (substring entry-main
960 (match-beginning daypos
)
961 (match-end daypos
))))
962 (month (icalendar--get-month-number
963 (substring entry-main
964 (match-beginning monpos
)
965 (match-end monpos
))))
966 (starttimestring (icalendar--diarytime-to-isotime
967 (if (match-beginning 4)
968 (substring entry-main
972 (if (match-beginning 5)
973 (substring entry-main
977 (endtimestring (icalendar--diarytime-to-isotime
978 (if (match-beginning 7)
979 (substring entry-main
983 (if (match-beginning 8)
984 (substring entry-main
988 (summary (icalendar--convert-string-for-export
989 (substring entry-main
(match-beginning 9)
991 (icalendar--dmsg "yearly %s" entry-main
)
993 (when starttimestring
994 (unless endtimestring
996 (icalendar--rris "^T0?" ""
998 (setq endtimestring
(format "T%06d"
1000 (concat "\nDTSTART;"
1001 (if starttimestring
"VALUE=DATE-TIME:"
1003 (format "1900%02d%02d" month day
)
1004 (or starttimestring
"")
1006 (if endtimestring
"VALUE=DATE-TIME:"
1008 ;; end is not included! shift by one day
1009 (icalendar--date-to-isodate
1010 (list month day
1900)
1011 (if endtimestring
0 1))
1012 (or endtimestring
"")
1015 "\nRRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH="
1016 (format "%2d" month
)
1018 (format "%2d" day
)))
1022 (defun icalendar--convert-sexp-to-ical (nonmarker entry-main
)
1023 "Convert complex sexp diary entry to icalendar format -- unsupported!
1027 NONMARKER is a regular expression matching the start of non-marking
1028 entries. ENTRY-MAIN is the first line of the diary entry."
1029 (if (string-match (concat nonmarker
1030 "%%(\\([^)]+\\))\\s-*\\(.*\\)")
1033 (icalendar--dmsg "diary-sexp %s" entry-main
)
1034 (error "Sexp-entries are not supported yet"))
1038 (defun icalendar--convert-block-to-ical (nonmarker entry-main
)
1039 "Convert block diary entry to icalendar format.
1041 NONMARKER is a regular expression matching the start of non-marking
1042 entries. ENTRY-MAIN is the first line of the diary entry."
1043 (if (string-match (concat nonmarker
1044 "%%(diary-block \\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\)"
1045 " +\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\))\\s-*"
1046 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
1048 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1052 (let* ((startstring (substring entry-main
1055 (endstring (substring entry-main
1058 (startisostring (icalendar--datestring-to-isodate
1060 (endisostring (icalendar--datestring-to-isodate
1062 (endisostring+1 (icalendar--datestring-to-isodate
1064 (starttimestring (icalendar--diarytime-to-isotime
1065 (if (match-beginning 4)
1066 (substring entry-main
1070 (if (match-beginning 5)
1071 (substring entry-main
1075 (endtimestring (icalendar--diarytime-to-isotime
1076 (if (match-beginning 7)
1077 (substring entry-main
1081 (if (match-beginning 8)
1082 (substring entry-main
1086 (summary (icalendar--convert-string-for-export
1087 (substring entry-main
(match-beginning 9)
1089 (icalendar--dmsg "diary-block %s" entry-main
)
1090 (when starttimestring
1091 (unless endtimestring
1093 (read (icalendar--rris "^T0?" ""
1095 (setq endtimestring
(format "T%06d"
1098 ;; with time -> write rrule
1099 (concat "\nDTSTART;VALUE=DATE-TIME:"
1102 "\nDTEND;VALUE=DATE-TIME:"
1107 "\nRRULE:FREQ=DAILY;INTERVAL=1;UNTIL="
1109 ;; no time -> write long event
1110 (concat "\nDTSTART;VALUE=DATE:" startisostring
1111 "\nDTEND;VALUE=DATE:" endisostring
+1
1112 "\nSUMMARY:" summary
)))
1116 (defun icalendar--convert-float-to-ical (nonmarker entry-main
)
1117 "Convert float diary entry to icalendar format -- unsupported!
1121 NONMARKER is a regular expression matching the start of non-marking
1122 entries. ENTRY-MAIN is the first line of the diary entry."
1123 (if (string-match (concat nonmarker
1124 "%%(diary-float \\([^)]+\\))\\s-*\\(.*\\)")
1127 (icalendar--dmsg "diary-float %s" entry-main
)
1128 (error "`diary-float' is not supported yet"))
1132 (defun icalendar--convert-date-to-ical (nonmarker entry-main
)
1133 "Convert `diary-date' diary entry to icalendar format -- unsupported!
1137 NONMARKER is a regular expression matching the start of non-marking
1138 entries. ENTRY-MAIN is the first line of the diary entry."
1139 (if (string-match (concat nonmarker
1140 "%%(diary-date \\([^)]+\\))\\s-*\\(.*\\)")
1143 (icalendar--dmsg "diary-date %s" entry-main
)
1144 (error "`diary-date' is not supported yet"))
1148 (defun icalendar--convert-cyclic-to-ical (nonmarker entry-main
)
1149 "Convert `diary-cyclic' diary entry to icalendar format.
1151 NONMARKER is a regular expression matching the start of non-marking
1152 entries. ENTRY-MAIN is the first line of the diary entry."
1153 (if (string-match (concat nonmarker
1154 "%%(diary-cyclic \\([^ ]+\\) +"
1155 "\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\))\\s-*"
1156 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
1158 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1162 (let* ((frequency (substring entry-main
(match-beginning 1)
1164 (datetime (substring entry-main
(match-beginning 2)
1166 (startisostring (icalendar--datestring-to-isodate
1168 (endisostring (icalendar--datestring-to-isodate
1170 (endisostring+1 (icalendar--datestring-to-isodate
1172 (starttimestring (icalendar--diarytime-to-isotime
1173 (if (match-beginning 4)
1174 (substring entry-main
1178 (if (match-beginning 5)
1179 (substring entry-main
1183 (endtimestring (icalendar--diarytime-to-isotime
1184 (if (match-beginning 7)
1185 (substring entry-main
1189 (if (match-beginning 8)
1190 (substring entry-main
1194 (summary (icalendar--convert-string-for-export
1195 (substring entry-main
(match-beginning 9)
1197 (icalendar--dmsg "diary-cyclic %s" entry-main
)
1198 (when starttimestring
1199 (unless endtimestring
1201 (read (icalendar--rris "^T0?" ""
1203 (setq endtimestring
(format "T%06d"
1205 (concat "\nDTSTART;"
1206 (if starttimestring
"VALUE=DATE-TIME:"
1209 (or starttimestring
"")
1211 (if endtimestring
"VALUE=DATE-TIME:"
1213 (if endtimestring endisostring endisostring
+1)
1214 (or endtimestring
"")
1215 "\nSUMMARY:" summary
1216 "\nRRULE:FREQ=DAILY;INTERVAL=" frequency
1217 ;; strange: korganizer does not expect
1218 ;; BYSOMETHING here...
1223 (defun icalendar--convert-anniversary-to-ical (nonmarker entry-main
)
1224 "Convert `diary-anniversary' diary entry to icalendar format.
1226 NONMARKER is a regular expression matching the start of non-marking
1227 entries. ENTRY-MAIN is the first line of the diary entry."
1228 (if (string-match (concat nonmarker
1229 "%%(diary-anniversary \\([^)]+\\))\\s-*"
1230 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
1232 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1236 (let* ((datetime (substring entry-main
(match-beginning 1)
1238 (startisostring (icalendar--datestring-to-isodate
1240 (endisostring (icalendar--datestring-to-isodate
1242 (starttimestring (icalendar--diarytime-to-isotime
1243 (if (match-beginning 3)
1244 (substring entry-main
1248 (if (match-beginning 4)
1249 (substring entry-main
1253 (endtimestring (icalendar--diarytime-to-isotime
1254 (if (match-beginning 6)
1255 (substring entry-main
1259 (if (match-beginning 7)
1260 (substring entry-main
1264 (summary (icalendar--convert-string-for-export
1265 (substring entry-main
(match-beginning 8)
1267 (icalendar--dmsg "diary-anniversary %s" entry-main
)
1268 (when starttimestring
1269 (unless endtimestring
1271 (read (icalendar--rris "^T0?" ""
1273 (setq endtimestring
(format "T%06d"
1275 (concat "\nDTSTART;"
1276 (if starttimestring
"VALUE=DATE-TIME:"
1279 (or starttimestring
"")
1281 (if endtimestring
"VALUE=DATE-TIME:"
1284 (or endtimestring
"")
1285 "\nSUMMARY:" summary
1286 "\nRRULE:FREQ=YEARLY;INTERVAL=1"
1287 ;; the following is redundant,
1288 ;; but korganizer seems to expect this... ;(
1289 ;; and evolution doesn't understand it... :(
1290 ;; so... who is wrong?!
1292 (substring startisostring
4 6)
1294 (substring startisostring
6 8)))
1298 ;; ======================================================================
1299 ;; Import -- convert icalendar to emacs-diary
1300 ;; ======================================================================
1303 (defun icalendar-import-file (ical-filename diary-filename
1304 &optional non-marking
)
1305 "Import a iCalendar file and append to a diary file.
1306 Argument ICAL-FILENAME output iCalendar file.
1307 Argument DIARY-FILENAME input `diary-file'.
1308 Optional argument NON-MARKING determines whether events are created as
1309 non-marking or not."
1310 (interactive "fImport iCalendar data from file:
1313 ;; clean up the diary file
1314 (save-current-buffer
1315 ;; now load and convert from the ical file
1316 (set-buffer (find-file ical-filename
))
1317 (icalendar-import-buffer diary-filename t non-marking
)))
1320 (defun icalendar-import-buffer (&optional diary-file do-not-ask
1322 "Extract iCalendar events from current buffer.
1324 This function searches the current buffer for the first iCalendar
1325 object, reads it and adds all VEVENT elements to the diary
1328 It will ask for each appointment whether to add it to the diary
1329 when DO-NOT-ASK is non-nil. When called interactively,
1330 DO-NOT-ASK is set to t, so that you are asked fore each event.
1332 NON-MARKING determines whether diary events are created as
1335 Return code t means that importing worked well, return code nil
1336 means that an error has occured. Error messages will be in the
1337 buffer `*icalendar-errors*'."
1339 (save-current-buffer
1341 (message "Preparing icalendar...")
1342 (set-buffer (icalendar--get-unfolded-buffer (current-buffer)))
1343 (goto-char (point-min))
1344 (message "Preparing icalendar...done")
1345 (if (re-search-forward "^BEGIN:VCALENDAR\\s-*$" nil t
)
1346 (let (ical-contents ical-errors
)
1348 (message "Reading icalendar...")
1350 (setq ical-contents
(icalendar--read-element nil nil
))
1351 (message "Reading icalendar...done")
1353 (message "Converting icalendar...")
1354 (setq ical-errors
(icalendar--convert-ical-to-diary
1356 diary-file do-not-ask non-marking
))
1358 ;; save the diary file if it is visited already
1359 (let ((b (find-buffer-visiting diary-file
)))
1361 (save-current-buffer
1364 (message "Converting icalendar...done")
1365 ;; return t if no error occured
1368 "Current buffer does not contain icalendar contents!")
1369 ;; return nil, i.e. import did not work
1372 (defalias 'icalendar-extract-ical-from-buffer
'icalendar-import-buffer
)
1373 (make-obsolete 'icalendar-extract-ical-from-buffer
'icalendar-import-buffer
)
1375 (defun icalendar--format-ical-event (event)
1376 "Create a string representation of an iCalendar EVENT."
1377 (let ((string icalendar-import-format
)
1379 '(("%d" DESCRIPTION icalendar-import-format-description
)
1380 ("%s" SUMMARY icalendar-import-format-subject
)
1381 ("%l" LOCATION icalendar-import-format-location
)
1382 ("%o" ORGANIZER icalendar-import-format-organizer
))))
1383 ;; convert the specifiers in the format string
1385 (let* ((spec (car i
))
1387 (format (car (cddr i
)))
1388 (contents (icalendar--get-event-property event prop
))
1389 (formatted-contents ""))
1390 (when (and contents
(> (length contents
) 0))
1391 (setq formatted-contents
1392 (icalendar--rris "%s"
1393 (icalendar--convert-string-for-import
1395 (symbol-value format
))))
1396 (setq string
(icalendar--rris spec
1402 (defun icalendar--convert-ical-to-diary (ical-list diary-file
1403 &optional do-not-ask
1405 "Convert an iCalendar file to an Emacs diary file.
1406 Import VEVENTS from the iCalendar object ICAL-LIST and saves them to a
1407 DIARY-FILE. If DO-NOT-ASK is nil the user is asked for each event
1408 whether to actually import it. NON-MARKING determines whether diary
1409 events are created as non-marking.
1410 This function attempts to return t if something goes wrong. In this
1411 case an error string which describes all the errors and problems is
1412 written into the buffer `*icalendar-errors*'."
1413 (let* ((ev (icalendar--all-events ical-list
))
1418 ;; step through all events/appointments
1423 (condition-case error-val
1424 (let* ((dtstart (icalendar--get-event-property e
'DTSTART
))
1425 (dtstart-dec (icalendar--decode-isodatetime dtstart
))
1426 (start-d (icalendar--datetime-to-diary-date
1428 (start-t (icalendar--datetime-to-colontime dtstart-dec
))
1429 (dtend (icalendar--get-event-property e
'DTEND
))
1430 (dtend-dec (icalendar--decode-isodatetime dtend
))
1431 (dtend-1-dec (icalendar--decode-isodatetime dtend -
1))
1435 (subject (icalendar--convert-string-for-import
1436 (or (icalendar--get-event-property e
'SUMMARY
)
1438 (rrule (icalendar--get-event-property e
'RRULE
))
1439 (rdate (icalendar--get-event-property e
'RDATE
))
1440 (duration (icalendar--get-event-property e
'DURATION
)))
1441 (icalendar--dmsg "%s: `%s'" start-d subject
)
1442 ;; check whether start-time is missing
1445 (cadr (icalendar--get-event-property-attributes
1450 (let ((dtend-dec-d (icalendar--add-decoded-times
1452 (icalendar--decode-isoduration duration
)))
1453 (dtend-1-dec-d (icalendar--add-decoded-times
1455 (icalendar--decode-isoduration duration
1457 (if (and dtend-dec
(not (eq dtend-dec dtend-dec-d
)))
1458 (message "Inconsistent endtime and duration for %s"
1460 (setq dtend-dec dtend-dec-d
)
1461 (setq dtend-1-dec dtend-1-dec-d
)))
1462 (setq end-d
(if dtend-dec
1463 (icalendar--datetime-to-diary-date dtend-dec
)
1465 (setq end-1-d
(if dtend-1-dec
1466 (icalendar--datetime-to-diary-date dtend-1-dec
)
1468 (setq end-t
(if (and
1472 (icalendar--get-event-property-attributes
1475 (icalendar--datetime-to-colontime dtend-dec
)
1477 (icalendar--dmsg "start-d: %s, end-d: %s" start-d end-d
)
1482 (icalendar--convert-recurring-to-diary e dtstart-dec start-t
1486 (icalendar--dmsg "rdate event")
1487 (setq diary-string
"")
1488 (mapcar (lambda (datestring)
1490 (concat diary-string
1491 (format "......"))))
1492 (icalendar--split-value rdate
)))
1493 ;; non-recurring event
1495 ((not (string= start-d end-d
))
1497 (icalendar--convert-non-recurring-all-day-to-diary
1501 ((and start-t
(or (not end-t
)
1502 (not (string= start-t end-t
))))
1504 (icalendar--convert-non-recurring-not-all-day-to-diary
1505 e dtstart-dec dtend-dec start-t end-t
))
1509 (icalendar--dmsg "all day event")
1510 (setq diary-string
(icalendar--datetime-to-diary-date
1513 ;; add all other elements unless the user doesn't want to have
1518 (concat diary-string
" "
1519 (icalendar--format-ical-event e
)))
1520 (if do-not-ask
(setq subject nil
))
1521 (icalendar--add-diary-entry diary-string diary-file
1522 non-marking subject
))
1524 (setq found-error t
)
1526 (format "%s\nCannot handle this event:%s"
1528 ;; FIXME: inform user about ignored event properties
1531 (message "Ignoring event \"%s\"" e
)
1532 (setq found-error t
)
1533 (setq error-string
(format "%s\n%s\nCannot handle this event: %s"
1534 error-val error-string e
))
1535 (message error-string
))))
1537 (save-current-buffer
1538 (set-buffer (get-buffer-create "*icalendar-errors*"))
1540 (insert error-string
)))
1541 (message "Converting icalendar...done")
1544 ;; subroutines for importing
1545 (defun icalendar--convert-recurring-to-diary (e dtstart-dec start-t end-t
)
1546 "Convert recurring icalendar event E to diary format.
1548 DTSTART-DEC is the DTSTART property of E.
1549 START-T is the event's start time in diary format.
1550 END-T is the event's end time in diary format."
1551 (icalendar--dmsg "recurring event")
1552 (let* ((rrule (icalendar--get-event-property e
'RRULE
))
1553 (rrule-props (icalendar--split-value rrule
))
1554 (frequency (cadr (assoc 'FREQ rrule-props
)))
1555 (until (cadr (assoc 'UNTIL rrule-props
)))
1556 (count (cadr (assoc 'COUNT rrule-props
)))
1557 (interval (read (or (cadr (assoc 'INTERVAL rrule-props
)) "1")))
1558 (dtstart-conv (icalendar--datetime-to-diary-date dtstart-dec
))
1559 (until-conv (icalendar--datetime-to-diary-date
1560 (icalendar--decode-isodatetime until
)))
1561 (until-1-conv (icalendar--datetime-to-diary-date
1562 (icalendar--decode-isodatetime until -
1)))
1565 ;; FIXME FIXME interval!!!!!!!!!!!!!
1569 (message "Must not have UNTIL and COUNT -- ignoring COUNT element!")
1571 (cond ((string-equal frequency
"DAILY")
1572 (setq until
(icalendar--add-decoded-times
1574 (list 0 0 0 (* (read count
) interval
) 0 0)))
1575 (setq until-1
(icalendar--add-decoded-times
1577 (list 0 0 0 (* (- (read count
) 1) interval
)
1580 ((string-equal frequency
"WEEKLY")
1581 (setq until
(icalendar--add-decoded-times
1583 (list 0 0 0 (* (read count
) 7 interval
) 0 0)))
1584 (setq until-1
(icalendar--add-decoded-times
1586 (list 0 0 0 (* (- (read count
) 1) 7
1589 ((string-equal frequency
"MONTHLY")
1590 (setq until
(icalendar--add-decoded-times
1591 dtstart-dec
(list 0 0 0 0 (* (- (read count
) 1)
1593 (setq until-1
(icalendar--add-decoded-times
1594 dtstart-dec
(list 0 0 0 0 (* (- (read count
) 1)
1597 ((string-equal frequency
"YEARLY")
1598 (setq until
(icalendar--add-decoded-times
1599 dtstart-dec
(list 0 0 0 0 0 (* (- (read count
) 1)
1601 (setq until-1
(icalendar--add-decoded-times
1603 (list 0 0 0 0 0 (* (- (read count
) 1)
1607 (message "Cannot handle COUNT attribute for `%s' events."
1609 (setq until-conv
(icalendar--datetime-to-diary-date until
))
1610 (setq until-1-conv
(icalendar--datetime-to-diary-date until-1
))
1613 (cond ((string-equal frequency
"WEEKLY")
1616 ;; weekly and all-day
1617 (icalendar--dmsg "weekly all-day")
1622 "(diary-cyclic %d %s) "
1623 "(diary-block %s %s))")
1627 (if count until-1-conv until-conv
)
1630 (format "%%%%(and (diary-cyclic %d %s))"
1633 ;; weekly and not all-day
1634 (let* ((byday (cadr (assoc 'BYDAY rrule-props
)))
1636 (icalendar--get-weekday-number byday
)))
1637 (icalendar--dmsg "weekly not-all-day")
1642 "(diary-cyclic %d %s) "
1643 "(diary-block %s %s)) "
1650 (if end-t
"-" "") (or end-t
"")))
1653 ;; DTSTART;VALUE=DATE-TIME:20030919T090000
1654 ;; DTEND;VALUE=DATE-TIME:20030919T113000
1657 "%%%%(and (diary-cyclic %s %s)) %s%s%s"
1661 (if end-t
"-" "") (or end-t
"")))))))
1663 ((string-equal frequency
"YEARLY")
1664 (icalendar--dmsg "yearly")
1666 (setq result
(format
1667 (concat "%%%%(and (diary-date %s %s t) "
1668 "(diary-block %s %s)) %s%s%s")
1669 (if european-calendar-style
(nth 3 dtstart-dec
)
1670 (nth 4 dtstart-dec
))
1671 (if european-calendar-style
(nth 4 dtstart-dec
)
1672 (nth 3 dtstart-dec
))
1676 (if end-t
"-" "") (or end-t
"")))
1677 (setq result
(format
1678 "%%%%(and (diary-anniversary %s)) %s%s%s"
1681 (if end-t
"-" "") (or end-t
"")))))
1683 ((string-equal frequency
"MONTHLY")
1684 (icalendar--dmsg "monthly")
1687 "%%%%(and (diary-date %s %s %s) (diary-block %s %s)) %s%s%s"
1688 (if european-calendar-style
(nth 3 dtstart-dec
) "t")
1689 (if european-calendar-style
"t" (nth 3 dtstart-dec
))
1694 "1 1 9999") ;; FIXME: should be unlimited
1696 (if end-t
"-" "") (or end-t
""))))
1698 ((and (string-equal frequency
"DAILY"))
1702 (concat "%%%%(and (diary-cyclic %s %s) "
1703 "(diary-block %s %s)) %s%s%s")
1704 interval dtstart-conv dtstart-conv
1705 (if count until-1-conv until-conv
)
1707 (if end-t
"-" "") (or end-t
"")))
1710 "%%%%(and (diary-cyclic %s %s)) %s%s%s"
1714 (if end-t
"-" "") (or end-t
""))))))
1715 ;; Handle exceptions from recurrence rules
1716 (let ((ex-dates (icalendar--get-event-properties e
'EXDATE
)))
1718 (let* ((ex-start (icalendar--decode-isodatetime
1720 (ex-d (icalendar--datetime-to-diary-date
1723 (icalendar--rris "^%%(\\(and \\)?"
1725 "%%%%(and (not (diary-date %s)) "
1728 (setq ex-dates
(cdr ex-dates
))))
1729 ;; FIXME: exception rules are not recognized
1730 (if (icalendar--get-event-property e
'EXRULE
)
1733 "\n Exception rules: "
1734 (icalendar--get-event-properties
1738 (defun icalendar--convert-non-recurring-all-day-to-diary (event start-d end-d
)
1739 "Convert non-recurring icalendar EVENT to diary format.
1741 DTSTART is the decoded DTSTART property of E.
1742 Argument START-D gives the first day.
1743 Argument END-D gives the last day."
1744 (icalendar--dmsg "non-recurring all-day event")
1745 (format "%%%%(and (diary-block %s %s))" start-d end-d
))
1747 (defun icalendar--convert-non-recurring-not-all-day-to-diary (event dtstart-dec
1751 "Convert recurring icalendar EVENT to diary format.
1753 DTSTART-DEC is the decoded DTSTART property of E.
1754 DTEND-DEC is the decoded DTEND property of E.
1755 START-T is the event's start time in diary format.
1756 END-T is the event's end time in diary format."
1757 (icalendar--dmsg "not all day event")
1760 (icalendar--datetime-to-diary-date
1765 (icalendar--datetime-to-diary-date
1769 (defun icalendar--add-diary-entry (string diary-file non-marking
1771 "Add STRING to the diary file DIARY-FILE.
1772 STRING must be a properly formatted valid diary entry. NON-MARKING
1773 determines whether diary events are created as non-marking. If
1774 SUBJECT is not nil it must be a string that gives the subject of the
1775 entry. In this case the user will be asked whether he wants to insert
1777 (when (or (not subject
)
1778 (y-or-n-p (format "Add appointment for `%s' to diary? "
1782 (y-or-n-p (format "Make appointment non-marking? "))))
1783 (save-window-excursion
1786 (read-file-name "Add appointment to this diary file: ")))
1787 (make-diary-entry string non-marking diary-file
))))
1789 (provide 'icalendar
)
1791 ;; arch-tag: 74fdbe8e-0451-4e38-bb61-4416e822f4fc
1792 ;;; icalendar.el ends here