1 ;;; icalendar.el --- iCalendar implementation -*-coding: utf-8 -*-
3 ;; Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
4 ;; Free Software Foundation, Inc.
6 ;; Author: Ulf Jasper <ulf.jasper@web.de>
7 ;; Created: August 2002
9 ;; Human-Keywords: calendar, diary, iCalendar, vCalendar
12 ;; This file is part of GNU Emacs.
14 ;; GNU Emacs is free software: you can redistribute it and/or modify
15 ;; it under the terms of the GNU General Public License as published by
16 ;; the Free Software Foundation, either version 3 of the License, or
17 ;; (at your option) any later version.
19 ;; GNU Emacs is distributed in the hope that it will be useful,
20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 ;; GNU General Public License for more details.
24 ;; You should have received a copy of the GNU General Public License
25 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
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
44 ;; - Bugfixes regarding icalendar-import-format-*.
45 ;; - Fix in icalendar-convert-diary-to-ical -- thanks to Philipp Grau.
48 ;; - New import format scheme: Replaced icalendar-import-prefix-*,
49 ;; icalendar-import-ignored-properties, and
50 ;; icalendar-import-separator with icalendar-import-format(-*).
51 ;; - icalendar-import-file and icalendar-convert-diary-to-ical
52 ;; have an extra parameter which should prevent them from
53 ;; erasing their target files (untested!).
54 ;; - Tested with Emacs 21.3.2
57 ;; - Bugfix: import: double quoted param values did not work
58 ;; - Read DURATION property when importing.
59 ;; - Added parameter icalendar-duration-correction.
62 ;; - Export takes care of european-calendar-style.
63 ;; - Tested with Emacs 21.3.2 and XEmacs 21.4.12
66 ;; - Should work in XEmacs now. Thanks to Len Trigg for the XEmacs patches!
67 ;; - Added exporting from Emacs diary to ical.
68 ;; - Some bugfixes, after testing with calendars from http://icalshare.com.
69 ;; - Tested with Emacs 21.3.2 and XEmacs 21.4.12
72 ;; - First published version. Trial version. Alpha version.
74 ;; ======================================================================
77 ;; * Import from ical to diary:
78 ;; + Need more properties for icalendar-import-format
79 ;; (added all that Mozilla Calendar uses)
80 ;; From iCal specifications (RFC2445: 4.8.1), icalendar.el lacks
81 ;; ATTACH, CATEGORIES, COMMENT, GEO, PERCENT-COMPLETE (VTODO),
82 ;; PRIORITY, RESOURCES) not considering date/time and time-zone
83 ;; + check vcalendar version
84 ;; + check (unknown) elements
85 ;; + recurring events!
86 ;; + works for european style calendars only! Does it?
88 ;; + exceptions in recurring events
89 ;; + the parser is too soft
90 ;; + error log is incomplete
91 ;; + nice to have: #include "webcal://foo.com/some-calendar.ics"
92 ;; + timezones probably still need some improvements.
94 ;; * Export from diary to ical
95 ;; + diary-date, diary-float, and self-made sexp entries are not
99 ;; + clean up all those date/time parsing functions
100 ;; + Handle todo items?
101 ;; + Check iso 8601 for datetime and period
102 ;; + Which chars to (un)escape?
107 (defconst icalendar-version
"0.19"
108 "Version number of icalendar.el.")
110 ;; ======================================================================
112 ;; ======================================================================
113 (defgroup icalendar nil
118 (defcustom icalendar-import-format
120 "Format for importing events from iCalendar into Emacs diary.
121 It defines how iCalendar events are inserted into diary file.
122 This may either be a string or a function.
124 In case of a formatting STRING the following specifiers can be used:
125 %c Class, see `icalendar-import-format-class'
126 %d Description, see `icalendar-import-format-description'
127 %l Location, see `icalendar-import-format-location'
128 %o Organizer, see `icalendar-import-format-organizer'
129 %s Summary, see `icalendar-import-format-summary'
130 %t Status, see `icalendar-import-format-status'
131 %u URL, see `icalendar-import-format-url'
133 A formatting FUNCTION will be called with a VEVENT as its only
134 argument. It must return a string. See
135 `icalendar-import-format-sample' for an example."
137 (string :tag
"String")
138 (function :tag
"Function"))
141 (defcustom icalendar-import-format-summary
143 "Format string defining how the summary element is formatted.
144 This applies only if the summary is not empty! `%s' is replaced
149 (defcustom icalendar-import-format-description
151 "Format string defining how the description element is formatted.
152 This applies only if the description is not empty! `%s' is
153 replaced by the description."
157 (defcustom icalendar-import-format-location
159 "Format string defining how the location element is formatted.
160 This applies only if the location is not empty! `%s' is replaced
165 (defcustom icalendar-import-format-organizer
167 "Format string defining how the organizer element is formatted.
168 This applies only if the organizer is not empty! `%s' is
169 replaced by the organizer."
173 (defcustom icalendar-import-format-url
175 "Format string defining how the URL element is formatted.
176 This applies only if the URL is not empty! `%s' is replaced by
181 (defcustom icalendar-import-format-status
183 "Format string defining how the status element is formatted.
184 This applies only if the status is not empty! `%s' is replaced by
189 (defcustom icalendar-import-format-class
191 "Format string defining how the class element is formatted.
192 This applies only if the class is not empty! `%s' is replaced by
197 (defcustom icalendar-recurring-start-year
199 "Start year for recurring events.
200 Some calendar browsers only propagate recurring events for
201 several years beyond the start time. Set this string to a year
202 just before the start of your personal calendar."
206 (defcustom icalendar-export-hidden-diary-entries
208 "Determines whether hidden diary entries are exported.
209 If non-nil hidden diary entries (starting with `&') get exported,
210 if nil they are ignored."
214 (defcustom icalendar-uid-format
216 "Format of unique ID code (UID) for each iCalendar object.
217 The following specifiers are available:
218 %c COUNTER, an integer value that is increased each time a uid is
219 generated. This may be necessary for systems which do not
220 provide time-resolution finer than a second.
221 %h HASH, a hash value of the diary entry,
222 %s DTSTART, the start date (excluding time) of the diary entry,
223 %t TIMESTAMP, a unique creation timestamp,
224 %u USERNAME, the variable `user-login-name'.
226 For example, a value of \"%s_%h@mydomain.com\" will generate a
227 UID code for each entry composed of the time of the event, a hash
228 code for the event, and your personal domain name."
232 (defvar icalendar-debug nil
233 "Enable icalendar debug messages.")
235 ;; ======================================================================
236 ;; NO USER SERVICABLE PARTS BELOW THIS LINE
237 ;; ======================================================================
239 (defconst icalendar--weekday-array
["SU" "MO" "TU" "WE" "TH" "FR" "SA"])
241 ;; ======================================================================
242 ;; all the other libs we need
243 ;; ======================================================================
246 ;; ======================================================================
248 ;; ======================================================================
249 (defun icalendar--dmsg (&rest args
)
250 "Print message ARGS if `icalendar-debug' is non-nil."
252 (apply 'message args
)))
254 ;; ======================================================================
255 ;; Core functionality
256 ;; Functions for parsing icalendars, importing and so on
257 ;; ======================================================================
259 (defun icalendar--get-unfolded-buffer (folded-ical-buffer)
260 "Return a new buffer containing the unfolded contents of a buffer.
261 Folding is the iCalendar way of wrapping long lines. In the
262 created buffer all occurrences of CR LF BLANK are replaced by the
263 empty string. Argument FOLDED-ICAL-BUFFER is the unfolded input
265 (let ((unfolded-buffer (get-buffer-create " *icalendar-work*")))
267 (set-buffer unfolded-buffer
)
269 (insert-buffer-substring folded-ical-buffer
)
270 (goto-char (point-min))
271 (while (re-search-forward "\r?\n[ \t]" nil t
)
272 (replace-match "" nil nil
)))
275 (defsubst icalendar--rris
(regexp rep string
&optional fixedcase literal
)
276 "Replace regular expression in string.
277 Pass arguments REGEXP REP STRING FIXEDCASE LITERAL to
278 `replace-regexp-in-string' (Emacs) or to `replace-in-string' (XEmacs)."
279 (cond ((fboundp 'replace-regexp-in-string
)
281 (replace-regexp-in-string regexp rep string fixedcase literal
))
282 ((fboundp 'replace-in-string
)
284 (save-match-data ;; apparently XEmacs needs save-match-data
285 (replace-in-string string regexp rep literal
)))))
287 (defun icalendar--read-element (invalue inparams
)
288 "Recursively read the next iCalendar element in the current buffer.
289 INVALUE gives the current iCalendar element we are reading.
290 INPARAMS gives the current parameters.....
291 This function calls itself recursively for each nested calendar element
293 (let (element children line name params param param-name param-value
298 (re-search-forward "^\\([A-Za-z0-9-]+\\)[;:]" nil t
))
299 (setq name
(intern (match-string 1)))
303 (while (looking-at ";")
304 (re-search-forward ";\\([A-Za-z0-9-]+\\)=" nil nil
)
305 (setq param-name
(intern (match-string 1)))
306 (re-search-forward "\\(\\([^;,:\"]+\\)\\|\"\\([^\"]+\\)\"\\)[;:]"
309 (setq param-value
(or (match-string 2) (match-string 3)))
310 (setq param
(list param-name param-value
))
311 (while (looking-at ",")
312 (re-search-forward "\\(\\([^;,:]+\\)\\|\"\\([^\"]+\\)\"\\)"
315 (setq param-value
(match-string 2))
316 (setq param-value
(match-string 3)))
317 (setq param
(append param param-value
)))
318 (setq params
(append params param
)))
319 (unless (looking-at ":")
322 (re-search-forward "\\(.*\\)\\(\r?\n[ \t].*\\)*" nil t
)
323 (setq value
(icalendar--rris "\r?\n[ \t]" "" (match-string 0)))
324 (setq line
(list name params value
))
325 (cond ((eq name
'BEGIN
)
328 (list (icalendar--read-element (intern value
)
333 (setq element
(append element
(list line
))))))
335 (list invalue inparams element children
)
338 ;; ======================================================================
339 ;; helper functions for examining events
340 ;; ======================================================================
342 ;;(defsubst icalendar--get-all-event-properties (event)
343 ;; "Return the list of properties in this EVENT."
344 ;; (car (cddr event)))
346 (defun icalendar--get-event-property (event prop
)
347 "For the given EVENT return the value of the first occurrence of PROP."
349 (let ((props (car (cddr event
))) pp
)
351 (setq pp
(car props
))
352 (if (eq (car pp
) prop
)
353 (throw 'found
(car (cddr pp
))))
354 (setq props
(cdr props
))))
357 (defun icalendar--get-event-property-attributes (event prop
)
358 "For the given EVENT return attributes of the first occurrence of PROP."
360 (let ((props (car (cddr event
))) pp
)
362 (setq pp
(car props
))
363 (if (eq (car pp
) prop
)
364 (throw 'found
(cadr pp
)))
365 (setq props
(cdr props
))))
368 (defun icalendar--get-event-properties (event prop
)
369 "For the given EVENT return a list of all values of the property PROP."
370 (let ((props (car (cddr event
))) pp result
)
372 (setq pp
(car props
))
373 (if (eq (car pp
) prop
)
374 (setq result
(append (split-string (car (cddr pp
)) ",") result
)))
375 (setq props
(cdr props
)))
378 ;; (defun icalendar--set-event-property (event prop new-value)
379 ;; "For the given EVENT set the property PROP to the value NEW-VALUE."
381 ;; (let ((props (car (cddr event))) pp)
383 ;; (setq pp (car props))
384 ;; (when (eq (car pp) prop)
385 ;; (setcdr (cdr pp) new-value)
386 ;; (throw 'found (car (cddr pp))))
387 ;; (setq props (cdr props)))
388 ;; (setq props (car (cddr event)))
389 ;; (setcar (cddr event)
390 ;; (append props (list (list prop nil new-value)))))))
392 (defun icalendar--get-children (node name
)
393 "Return all children of the given NODE which have a name NAME.
394 For instance the VCALENDAR node can have VEVENT children as well as VTODO
397 (children (cadr (cddr node
))))
398 (when (eq (car node
) name
)
400 ;;(message "%s" node)
405 (icalendar--get-children n name
))
409 (setq result
(append result subresult
))
410 (setq result subresult
)))))
414 (defun icalendar--all-events (icalendar)
415 "Return the list of all existing events in the given ICALENDAR."
416 (icalendar--get-children (car icalendar
) 'VEVENT
))
418 (defun icalendar--split-value (value-string)
419 "Split VALUE-STRING at ';='."
421 param-name param-value
)
424 (set-buffer (get-buffer-create " *icalendar-work*"))
425 (set-buffer-modified-p nil
)
427 (insert value-string
)
428 (goto-char (point-min))
431 "\\([A-Za-z0-9-]+\\)=\\(\\([^;:]+\\)\\|\"\\([^\"]+\\)\"\\);?"
433 (setq param-name
(intern (match-string 1)))
434 (setq param-value
(match-string 2))
436 (append result
(list (list param-name param-value
)))))))
439 (defun icalendar--convert-tz-offset (alist dst-p
)
440 "Return a cons of two strings representing a timezone start.
441 ALIST is an alist entry from a VTIMEZONE, like STANDARD.
442 DST-P is non-nil if this is for daylight savings time.
443 The strings are suitable for assembling into a TZ variable."
444 (let ((offset (car (cddr (assq 'TZOFFSETTO alist
))))
445 (rrule-value (car (cddr (assq 'RRULE alist
))))
446 (dtstart (car (cddr (assq 'DTSTART alist
)))))
447 ;; FIXME: for now we only handle RRULE and not RDATE here.
448 (when (and offset rrule-value dtstart
)
449 (let* ((rrule (icalendar--split-value rrule-value
))
450 (freq (cadr (assq 'FREQ rrule
)))
451 (bymonth (cadr (assq 'BYMONTH rrule
)))
452 (byday (cadr (assq 'BYDAY rrule
))))
453 ;; FIXME: we don't correctly handle WKST here.
454 (if (and (string= freq
"YEARLY") bymonth
)
458 (if dst-p
"DST" "STD")
459 ;; For TZ, OFFSET is added to the local time. So,
460 ;; invert the values.
461 (if (eq (aref offset
0) ?-
) "+" "-")
462 (substring offset
1 3)
464 (substring offset
3 5))
466 (let* ((day (icalendar--get-weekday-number (substring byday -
2)))
467 (week (if (eq day -
1)
469 (substring byday
0 -
2))))
470 ;; "Translate" the icalendar way to specify the last
471 ;; (sun|mon|...)day in month to the tzset way.
472 (if (string= week
"-1") ; last day as icalendar calls it
473 (setq week
"5")) ; last day as tzset calls it
474 (concat "M" bymonth
"." week
"." (if (eq day -
1) "0"
478 (substring dtstart -
6 -
4)
480 (substring dtstart -
4 -
2)
482 (substring dtstart -
2)))))))))
484 (defun icalendar--parse-vtimezone (alist)
485 "Turn a VTIMEZONE ALIST into a cons (ID . TZ-STRING).
486 Return nil if timezone cannot be parsed."
487 (let* ((tz-id (icalendar--get-event-property alist
'TZID
))
488 (daylight (cadr (cdar (icalendar--get-children alist
'DAYLIGHT
))))
489 (day (and daylight
(icalendar--convert-tz-offset daylight t
)))
490 (standard (cadr (cdar (icalendar--get-children alist
'STANDARD
))))
491 (std (and standard
(icalendar--convert-tz-offset standard nil
))))
495 (concat (car std
) (car day
)
496 "," (cdr day
) "," (cdr std
))
499 (defun icalendar--convert-all-timezones (icalendar)
500 "Convert all timezones in the ICALENDAR into an alist.
501 Each element of the alist is a cons (ID . TZ-STRING),
502 like `icalendar--parse-vtimezone'."
504 (dolist (zone (icalendar--get-children (car icalendar
) 'VTIMEZONE
))
505 (setq zone
(icalendar--parse-vtimezone zone
))
507 (setq result
(cons zone result
))))
510 (defun icalendar--find-time-zone (prop-list zone-map
)
511 "Return a timezone string for the time zone in PROP-LIST, or nil if none.
512 ZONE-MAP is a timezone alist as returned by `icalendar--convert-all-timezones'."
513 (let ((id (plist-get prop-list
'TZID
)))
515 (cdr (assoc id zone-map
)))))
517 (defun icalendar--decode-isodatetime (isodatetimestring &optional day-shift
519 "Return ISODATETIMESTRING in format like `decode-time'.
520 Converts from ISO-8601 to Emacs representation. If
521 ISODATETIMESTRING specifies UTC time (trailing letter Z) the
522 decoded time is given in the local time zone! If optional
523 parameter DAY-SHIFT is non-nil the result is shifted by DAY-SHIFT
525 ZONE, if provided, is the timezone, in any format understood by `encode-time'.
527 FIXME: multiple comma-separated values should be allowed!"
528 (icalendar--dmsg isodatetimestring
)
529 (if isodatetimestring
530 ;; day/month/year must be present
531 (let ((year (read (substring isodatetimestring
0 4)))
532 (month (read (substring isodatetimestring
4 6)))
533 (day (read (substring isodatetimestring
6 8)))
537 (when (> (length isodatetimestring
) 12)
538 ;; hour/minute present
539 (setq hour
(read (substring isodatetimestring
9 11)))
540 (setq minute
(read (substring isodatetimestring
11 13))))
541 (when (> (length isodatetimestring
) 14)
543 (setq second
(read (substring isodatetimestring
13 15))))
544 (when (and (> (length isodatetimestring
) 15)
545 ;; UTC specifier present
546 (char-equal ?Z
(aref isodatetimestring
15)))
547 ;; if not UTC add current-time-zone offset
548 (setq second
(+ (car (current-time-zone)) second
)))
549 ;; shift if necessary
551 (let ((mdy (calendar-gregorian-from-absolute
552 (+ (calendar-absolute-from-gregorian
553 (list month day year
))
555 (setq month
(nth 0 mdy
))
556 (setq day
(nth 1 mdy
))
557 (setq year
(nth 2 mdy
))))
558 ;; create the decoded date-time
561 (decode-time (encode-time second minute hour day month year zone
))
563 (message "Cannot decode \"%s\"" isodatetimestring
)
564 ;; hope for the best...
565 (list second minute hour day month year
0 nil
0))))
566 ;; isodatetimestring == nil
569 (defun icalendar--decode-isoduration (isodurationstring
570 &optional duration-correction
)
571 "Convert ISODURATIONSTRING into format provided by `decode-time'.
572 Converts from ISO-8601 to Emacs representation. If ISODURATIONSTRING
573 specifies UTC time (trailing letter Z) the decoded time is given in
576 Optional argument DURATION-CORRECTION shortens result by one day.
578 FIXME: TZID-attributes are ignored....!
579 FIXME: multiple comma-separated values should be allowed!"
580 (if isodurationstring
585 "\\(\\([0-9]+\\)D\\)" ; days only
587 "\\(\\(\\([0-9]+\\)D\\)?T\\(\\([0-9]+\\)H\\)?" ; opt days
588 "\\(\\([0-9]+\\)M\\)?\\(\\([0-9]+\\)S\\)?\\)" ; mand. time
590 "\\(\\([0-9]+\\)W\\)" ; weeks only
591 "\\)$") isodurationstring
)
599 ((match-beginning 2) ;days only
600 (setq days
(read (substring isodurationstring
603 (when duration-correction
604 (setq days
(1- days
))))
605 ((match-beginning 4) ;days and time
606 (if (match-beginning 5)
607 (setq days
(* 7 (read (substring isodurationstring
610 (if (match-beginning 7)
611 (setq hours
(read (substring isodurationstring
614 (if (match-beginning 9)
615 (setq minutes
(read (substring isodurationstring
618 (if (match-beginning 11)
619 (setq seconds
(read (substring isodurationstring
622 ((match-beginning 13) ;weeks only
623 (setq days
(* 7 (read (substring isodurationstring
626 (list seconds minutes hours days months years
)))
627 ;; isodatetimestring == nil
630 (defun icalendar--add-decoded-times (time1 time2
)
632 Both times must be given in decoded form. One of these times must be
633 valid (year > 1900 or something)."
634 ;; FIXME: does this function exist already?
635 (decode-time (encode-time
636 (+ (nth 0 time1
) (nth 0 time2
))
637 (+ (nth 1 time1
) (nth 1 time2
))
638 (+ (nth 2 time1
) (nth 2 time2
))
639 (+ (nth 3 time1
) (nth 3 time2
))
640 (+ (nth 4 time1
) (nth 4 time2
))
641 (+ (nth 5 time1
) (nth 5 time2
))
644 ;;(or (nth 6 time1) (nth 6 time2)) ;; FIXME?
647 (defun icalendar--datetime-to-american-date (datetime &optional separator
)
648 "Convert the decoded DATETIME to American-style format.
649 Optional argument SEPARATOR gives the separator between month,
650 day, and year. If nil a blank character is used as separator.
651 American format: \"month day year\"."
653 (format "%d%s%d%s%d" (nth 4 datetime
) ;month
655 (nth 3 datetime
) ;day
657 (nth 5 datetime
)) ;year
661 (define-obsolete-function-alias 'icalendar--datetime-to-noneuropean-date
662 'icalendar--datetime-to-american-date
"icalendar 0.19")
664 (defun icalendar--datetime-to-european-date (datetime &optional separator
)
665 "Convert the decoded DATETIME to European format.
666 Optional argument SEPARATOR gives the separator between month,
667 day, and year. If nil a blank character is used as separator.
668 European format: (day month year).
671 (format "%d%s%d%s%d" (nth 3 datetime
) ;day
673 (nth 4 datetime
) ;month
675 (nth 5 datetime
)) ;year
679 (defun icalendar--datetime-to-iso-date (datetime &optional separator
)
680 "Convert the decoded DATETIME to ISO format.
681 Optional argument SEPARATOR gives the separator between month,
682 day, and year. If nil a blank character is used as separator.
683 ISO format: (year month day)."
685 (format "%d%s%d%s%d" (nth 5 datetime
) ;year
687 (nth 4 datetime
) ;month
689 (nth 3 datetime
)) ;day
693 (defun icalendar--date-style ()
694 "Return current calendar date style.
695 Convenience function to handle transition from old
696 `european-calendar-style' to new `calendar-date-style'."
697 (if (boundp 'calendar-date-style
)
699 (if (with-no-warnings european-calendar-style
)
703 (defun icalendar--datetime-to-diary-date (datetime &optional separator
)
704 "Convert the decoded DATETIME to diary format.
705 Optional argument SEPARATOR gives the separator between month,
706 day, and year. If nil a blank character is used as separator.
707 Call icalendar--datetime-to-*-date according to the current
708 calendar date style."
709 (funcall (intern-soft (format "icalendar--datetime-to-%s-date"
710 (icalendar--date-style)))
713 (defun icalendar--datetime-to-colontime (datetime)
714 "Extract the time part of a decoded DATETIME into 24-hour format.
715 Note that this silently ignores seconds."
716 (format "%02d:%02d" (nth 2 datetime
) (nth 1 datetime
)))
718 (defun icalendar--get-month-number (monthname)
719 "Return the month number for the given MONTHNAME."
722 (m (downcase monthname
)))
723 (mapc (lambda (month)
724 (let ((mm (downcase month
)))
725 (if (or (string-equal mm m
)
726 (string-equal (substring mm
0 3) m
))
728 (setq num
(1+ num
))))
729 calendar-month-name-array
))
733 (defun icalendar--get-weekday-number (abbrevweekday)
734 "Return the number for the ABBREVWEEKDAY."
738 (aw (downcase abbrevweekday
)))
740 (let ((d (downcase day
)))
741 (if (string-equal d aw
)
743 (setq num
(1+ num
))))
744 icalendar--weekday-array
)))
748 (defun icalendar--get-weekday-numbers (abbrevweekdays)
749 "Return the list of numbers for the comma-separated ABBREVWEEKDAYS."
752 (weekday-alist (mapcar (lambda (day)
755 (cons (downcase day
) num
)))
756 icalendar--weekday-array
)))
758 (mapcar (lambda (abbrevday)
759 (cdr (assoc abbrevday weekday-alist
)))
760 (split-string (downcase abbrevweekdays
) ","))))))
762 (defun icalendar--get-weekday-abbrev (weekday)
763 "Return the abbreviated WEEKDAY."
766 (w (downcase weekday
)))
768 (let ((d (downcase day
)))
769 (if (or (string-equal d w
)
770 (string-equal (substring d
0 3) w
))
771 (throw 'found
(aref icalendar--weekday-array num
)))
772 (setq num
(1+ num
))))
773 calendar-day-name-array
))
777 (defun icalendar--date-to-isodate (date &optional day-shift
)
778 "Convert DATE to iso-style date.
779 DATE must be a list of the form (month day year).
780 If DAY-SHIFT is non-nil, the result is shifted by DAY-SHIFT days."
781 (let ((mdy (calendar-gregorian-from-absolute
782 (+ (calendar-absolute-from-gregorian date
)
784 (format "%04d%02d%02d" (nth 2 mdy
) (nth 0 mdy
) (nth 1 mdy
))))
787 (defun icalendar--datestring-to-isodate (datestring &optional day-shift
)
788 "Convert diary-style DATESTRING to iso-style date.
789 If DAY-SHIFT is non-nil, the result is shifted by DAY-SHIFT days
790 -- DAY-SHIFT must be either nil or an integer. This function
791 tries to figure the date style from DATESTRING itself. If that
792 is not possible it uses the current calendar date style."
793 (let ((day -
1) month year
)
795 (cond ( ;; iso-style numeric date
796 (string-match (concat "\\s-*"
797 "\\([0-9]\\{4\\}\\)[ \t/]\\s-*"
798 "0?\\([1-9][0-9]?\\)[ \t/]\\s-*"
799 "0?\\([1-9][0-9]?\\)")
801 (setq year
(read (substring datestring
(match-beginning 1)
803 (setq month
(read (substring datestring
(match-beginning 2)
805 (setq day
(read (substring datestring
(match-beginning 3)
807 ( ;; non-iso numeric date -- must rely on configured
809 (string-match (concat "\\s-*"
810 "0?\\([1-9][0-9]?\\)[ \t/]\\s-*"
811 "0?\\([1-9][0-9]?\\),?[ \t/]\\s-*"
812 "\\([0-9]\\{4\\}\\)")
814 (setq day
(read (substring datestring
(match-beginning 1)
816 (setq month
(read (substring datestring
(match-beginning 2)
818 (setq year
(read (substring datestring
(match-beginning 3)
820 (if (eq (icalendar--date-style) 'american
)
824 ( ;; date contains month names -- iso style
825 (string-match (concat "\\s-*"
826 "\\([0-9]\\{4\\}\\)[ \t/]\\s-*"
827 "\\([A-Za-z][^ ]+\\)[ \t/]\\s-*"
828 "0?\\([123]?[0-9]\\)")
830 (setq year
(read (substring datestring
(match-beginning 1)
832 (setq month
(icalendar--get-month-number
833 (substring datestring
(match-beginning 2)
835 (setq day
(read (substring datestring
(match-beginning 3)
837 ( ;; date contains month names -- european style
838 (string-match (concat "\\s-*"
839 "0?\\([123]?[0-9]\\)[ \t/]\\s-*"
840 "\\([A-Za-z][^ ]+\\)[ \t/]\\s-*"
841 "\\([0-9]\\{4\\}\\)")
843 (setq day
(read (substring datestring
(match-beginning 1)
845 (setq month
(icalendar--get-month-number
846 (substring datestring
(match-beginning 2)
848 (setq year
(read (substring datestring
(match-beginning 3)
850 ( ;; date contains month names -- american style
851 (string-match (concat "\\s-*"
852 "\\([A-Za-z][^ ]+\\)[ \t/]\\s-*"
853 "0?\\([123]?[0-9]\\),?[ \t/]\\s-*"
854 "\\([0-9]\\{4\\}\\)")
856 (setq day
(read (substring datestring
(match-beginning 2)
858 (setq month
(icalendar--get-month-number
859 (substring datestring
(match-beginning 1)
861 (setq year
(read (substring datestring
(match-beginning 3)
866 (let ((mdy (calendar-gregorian-from-absolute
867 (+ (calendar-absolute-from-gregorian (list month day
870 (icalendar--dmsg (format "%04d%02d%02d" (nth 2 mdy
) (nth 0 mdy
) (nth 1 mdy
)))
871 (format "%04d%02d%02d" (nth 2 mdy
) (nth 0 mdy
) (nth 1 mdy
)))
874 (defun icalendar--diarytime-to-isotime (timestring ampmstring
)
875 "Convert a time like 9:30pm to an iso-conform string like T213000.
876 In this example the TIMESTRING would be \"9:30\" and the AMPMSTRING
879 (let ((starttimenum (read (icalendar--rris ":" "" timestring
))))
880 ;; take care of am/pm style
881 ;; Be sure *not* to convert 12:00pm - 12:59pm to 2400-2459
882 (if (and ampmstring
(string= "pm" ampmstring
) (< starttimenum
1200))
883 (setq starttimenum
(+ starttimenum
1200)))
884 ;; Similar effect with 12:00am - 12:59am (need to convert to 0000-0059)
885 (if (and ampmstring
(string= "am" ampmstring
) (>= starttimenum
1200))
886 (setq starttimenum
(- starttimenum
1200)))
887 (format "T%04d00" starttimenum
))
890 (defun icalendar--convert-string-for-export (string)
891 "Escape comma and other critical characters in STRING."
892 (icalendar--rris "," "\\\\," string
))
894 (defun icalendar--convert-string-for-import (string)
895 "Remove escape chars for comma, semicolon etc. from STRING."
897 "\\\\n" "\n " (icalendar--rris
898 "\\\\\"" "\"" (icalendar--rris
899 "\\\\;" ";" (icalendar--rris
900 "\\\\," "," string
)))))
902 ;; ======================================================================
903 ;; Export -- convert emacs-diary to icalendar
904 ;; ======================================================================
907 (defun icalendar-export-file (diary-filename ical-filename
)
908 "Export diary file to iCalendar format.
909 All diary entries in the file DIARY-FILENAME are converted to iCalendar
910 format. The result is appended to the file ICAL-FILENAME."
911 (interactive "FExport diary data from file:
912 Finto iCalendar file: ")
914 (set-buffer (find-file diary-filename
))
915 (icalendar-export-region (point-min) (point-max) ical-filename
)))
917 (defalias 'icalendar-convert-diary-to-ical
'icalendar-export-file
)
918 (make-obsolete 'icalendar-convert-diary-to-ical
'icalendar-export-file
"22.1")
920 (defvar icalendar--uid-count
0
921 "Auxiliary counter for creating unique ids.")
923 (defun icalendar--create-uid (entry-full contents
)
924 "Construct a unique iCalendar UID for a diary entry.
925 ENTRY-FULL is the full diary entry string. CONTENTS is the
926 current iCalendar object, as a string. Increase
927 `icalendar--uid-count'. Returns the UID string."
928 (let ((uid icalendar-uid-format
))
930 (setq uid
(replace-regexp-in-string
932 (format "%d" icalendar--uid-count
)
934 (setq icalendar--uid-count
(1+ icalendar--uid-count
))
935 (setq uid
(replace-regexp-in-string
937 (format "%d%d%d" (car (current-time))
938 (cadr (current-time))
939 (car (cddr (current-time))))
941 (setq uid
(replace-regexp-in-string
943 (format "%d" (abs (sxhash entry-full
))) uid t t
))
944 (setq uid
(replace-regexp-in-string
945 "%u" (or user-login-name
"UNKNOWN_USER") uid t t
))
946 (let ((dtstart (if (string-match "^DTSTART[^:]*:\\([0-9]*\\)" contents
)
947 (substring contents
(match-beginning 1) (match-end 1))
949 (setq uid
(replace-regexp-in-string "%s" dtstart uid t t
)))
951 ;; Return the UID string
955 (defun icalendar-export-region (min max ical-filename
)
956 "Export region in diary file to iCalendar format.
957 All diary entries in the region from MIN to MAX in the current buffer are
958 converted to iCalendar format. The result is appended to the file
960 This function attempts to return t if something goes wrong. In this
961 case an error string which describes all the errors and problems is
962 written into the buffer `*icalendar-errors*'."
964 FExport diary data into iCalendar file: ")
974 (nonmarker (concat "^" (regexp-quote diary-nonmarking-symbol
)
976 (other-elements nil
))
977 ;; prepare buffer with error messages
979 (set-buffer (get-buffer-create "*icalendar-errors*"))
985 (while (re-search-forward
986 ;; possibly ignore hidden entries beginning with "&"
987 (if icalendar-export-hidden-diary-entries
988 "^\\([^ \t\n#].+\\)\\(\\(\n[ \t].*\\)*\\)"
989 "^\\([^ \t\n&#].+\\)\\(\\(\n[ \t].*\\)*\\)") max t
)
990 (setq entry-main
(match-string 1))
991 (if (match-beginning 2)
992 (setq entry-rest
(match-string 2))
993 (setq entry-rest
""))
994 (setq entry-full
(concat entry-main entry-rest
))
996 (condition-case error-val
998 (setq contents-n-summary
999 (icalendar--convert-to-ical nonmarker entry-main
))
1000 (setq other-elements
(icalendar--parse-summary-and-rest
1002 (setq contents
(concat (car contents-n-summary
)
1003 "\nSUMMARY:" (cadr contents-n-summary
)))
1004 (let ((cla (cdr (assoc 'cla other-elements
)))
1005 (des (cdr (assoc 'des other-elements
)))
1006 (loc (cdr (assoc 'loc other-elements
)))
1007 (org (cdr (assoc 'org other-elements
)))
1008 (sta (cdr (assoc 'sta other-elements
)))
1009 (sum (cdr (assoc 'sum other-elements
)))
1010 (url (cdr (assoc 'url other-elements
))))
1012 (setq contents
(concat contents
"\nCLASS:" cla
)))
1014 (setq contents
(concat contents
"\nDESCRIPTION:" des
)))
1016 (setq contents
(concat contents
"\nLOCATION:" loc
)))
1018 (setq contents
(concat contents
"\nORGANIZER:" org
)))
1020 (setq contents
(concat contents
"\nSTATUS:" sta
)))
1022 ;; (setq contents (concat contents "\nSUMMARY:" sum)))
1024 (setq contents
(concat contents
"\nURL:" url
))))
1026 (setq header
(concat "\nBEGIN:VEVENT\nUID:"
1027 (icalendar--create-uid entry-full contents
)))
1028 (setq result
(concat result header contents
"\nEND:VEVENT")))
1031 (setq found-error t
)
1032 (save-current-buffer
1033 (set-buffer (get-buffer-create "*icalendar-errors*"))
1034 (insert (format "Error in line %d -- %s: `%s'\n"
1035 (count-lines (point-min) (point))
1039 ;; we're done, insert everything into the file
1040 (save-current-buffer
1041 (let ((coding-system-for-write 'utf-8
))
1042 (set-buffer (find-file ical-filename
))
1043 (goto-char (point-max))
1044 (insert "BEGIN:VCALENDAR")
1045 (insert "\nPRODID:-//Emacs//NONSGML icalendar.el//EN")
1046 (insert "\nVERSION:2.0")
1048 (insert "\nEND:VCALENDAR\n")
1049 ;; save the diary file
1055 (defun icalendar--convert-to-ical (nonmarker entry-main
)
1056 "Convert a diary entry to icalendar format.
1057 NONMARKER is a regular expression matching the start of non-marking
1058 entries. ENTRY-MAIN is the first line of the diary entry."
1060 ;; anniversaries -- %%(diary-anniversary ...)
1061 (icalendar--convert-anniversary-to-ical nonmarker entry-main
)
1062 ;; cyclic events -- %%(diary-cyclic ...)
1063 (icalendar--convert-cyclic-to-ical nonmarker entry-main
)
1064 ;; diary-date -- %%(diary-date ...)
1065 (icalendar--convert-date-to-ical nonmarker entry-main
)
1066 ;; float events -- %%(diary-float ...)
1067 (icalendar--convert-float-to-ical nonmarker entry-main
)
1068 ;; block events -- %%(diary-block ...)
1069 (icalendar--convert-block-to-ical nonmarker entry-main
)
1070 ;; other sexp diary entries
1071 (icalendar--convert-sexp-to-ical nonmarker entry-main
)
1072 ;; weekly by day -- Monday 8:30 Team meeting
1073 (icalendar--convert-weekly-to-ical nonmarker entry-main
)
1074 ;; yearly by day -- 1 May Tag der Arbeit
1075 (icalendar--convert-yearly-to-ical nonmarker entry-main
)
1076 ;; "ordinary" events, start and end time given
1078 (icalendar--convert-ordinary-to-ical nonmarker entry-main
)
1080 ;; Oops! what's that?
1081 (error "Could not parse entry")))
1083 (defun icalendar--parse-summary-and-rest (summary-and-rest)
1084 "Parse SUMMARY-AND-REST from a diary to fill iCalendar properties.
1087 (if (functionp icalendar-import-format
)
1088 ;; can't do anything
1090 ;; split summary-and-rest
1091 (let* ((s icalendar-import-format
)
1092 (p-cla (or (string-match "%c" icalendar-import-format
) -
1))
1093 (p-des (or (string-match "%d" icalendar-import-format
) -
1))
1094 (p-loc (or (string-match "%l" icalendar-import-format
) -
1))
1095 (p-org (or (string-match "%o" icalendar-import-format
) -
1))
1096 (p-sum (or (string-match "%s" icalendar-import-format
) -
1))
1097 (p-sta (or (string-match "%t" icalendar-import-format
) -
1))
1098 (p-url (or (string-match "%u" icalendar-import-format
) -
1))
1099 (p-list (sort (list p-cla p-des p-loc p-org p-sta p-sum p-url
) '<))
1101 pos-cla pos-des pos-loc pos-org pos-sta pos-sum pos-url
)
1102 (dotimes (i (length p-list
))
1103 ;; Use 'ct' to keep track of current position in list
1104 (cond ((and (>= p-cla
0) (= (nth i p-list
) p-cla
))
1106 (setq pos-cla
(* 2 ct
)))
1107 ((and (>= p-des
0) (= (nth i p-list
) p-des
))
1109 (setq pos-des
(* 2 ct
)))
1110 ((and (>= p-loc
0) (= (nth i p-list
) p-loc
))
1112 (setq pos-loc
(* 2 ct
)))
1113 ((and (>= p-org
0) (= (nth i p-list
) p-org
))
1115 (setq pos-org
(* 2 ct
)))
1116 ((and (>= p-sta
0) (= (nth i p-list
) p-sta
))
1118 (setq pos-sta
(* 2 ct
)))
1119 ((and (>= p-sum
0) (= (nth i p-list
) p-sum
))
1121 (setq pos-sum
(* 2 ct
)))
1122 ((and (>= p-url
0) (= (nth i p-list
) p-url
))
1124 (setq pos-url
(* 2 ct
)))) )
1126 (setq s
(icalendar--rris (car ij
) (cadr ij
) s t t
)))
1128 ;; summary must be first! because of %s
1130 (concat "\\(" icalendar-import-format-summary
"\\)??"))
1132 (concat "\\(" icalendar-import-format-class
"\\)??"))
1134 (concat "\\(" icalendar-import-format-description
"\\)??"))
1136 (concat "\\(" icalendar-import-format-location
"\\)??"))
1138 (concat "\\(" icalendar-import-format-organizer
"\\)??"))
1140 (concat "\\(" icalendar-import-format-status
"\\)??"))
1142 (concat "\\(" icalendar-import-format-url
"\\)??"))))
1143 ;; Need the \' regexp in order to detect multi-line items
1144 (setq s
(concat "\\`"
1145 (icalendar--rris "%s" "\\(.*?\\)" s nil t
)
1147 (if (string-match s summary-and-rest
)
1148 (let (cla des loc org sta sum url
)
1149 (if (and pos-sum
(match-beginning pos-sum
))
1150 (setq sum
(substring summary-and-rest
1151 (match-beginning pos-sum
)
1152 (match-end pos-sum
))))
1153 (if (and pos-cla
(match-beginning pos-cla
))
1154 (setq cla
(substring summary-and-rest
1155 (match-beginning pos-cla
)
1156 (match-end pos-cla
))))
1157 (if (and pos-des
(match-beginning pos-des
))
1158 (setq des
(substring summary-and-rest
1159 (match-beginning pos-des
)
1160 (match-end pos-des
))))
1161 (if (and pos-loc
(match-beginning pos-loc
))
1162 (setq loc
(substring summary-and-rest
1163 (match-beginning pos-loc
)
1164 (match-end pos-loc
))))
1165 (if (and pos-org
(match-beginning pos-org
))
1166 (setq org
(substring summary-and-rest
1167 (match-beginning pos-org
)
1168 (match-end pos-org
))))
1169 (if (and pos-sta
(match-beginning pos-sta
))
1170 (setq sta
(substring summary-and-rest
1171 (match-beginning pos-sta
)
1172 (match-end pos-sta
))))
1173 (if (and pos-url
(match-beginning pos-url
))
1174 (setq url
(substring summary-and-rest
1175 (match-beginning pos-url
)
1176 (match-end pos-url
))))
1177 (list (if cla
(cons 'cla cla
) nil
)
1178 (if des
(cons 'des des
) nil
)
1179 (if loc
(cons 'loc loc
) nil
)
1180 (if org
(cons 'org org
) nil
)
1181 (if sta
(cons 'sta sta
) nil
)
1182 ;;(if sum (cons 'sum sum) nil)
1183 (if url
(cons 'url url
) nil
))))))))
1185 ;; subroutines for icalendar-export-region
1186 (defun icalendar--convert-ordinary-to-ical (nonmarker entry-main
)
1187 "Convert \"ordinary\" diary entry to icalendar format.
1188 NONMARKER is a regular expression matching the start of non-marking
1189 entries. ENTRY-MAIN is the first line of the diary entry."
1192 "\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\)\\s-*" ; date
1193 "\\(\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?" ; start time
1195 "-\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?" ; end time
1197 "\\s-*\\(.*?\\) ?$")
1199 (let* ((datetime (substring entry-main
(match-beginning 1)
1201 (startisostring (icalendar--datestring-to-isodate
1203 (endisostring (icalendar--datestring-to-isodate
1206 (starttimestring (icalendar--diarytime-to-isotime
1207 (if (match-beginning 3)
1208 (substring entry-main
1212 (if (match-beginning 4)
1213 (substring entry-main
1217 (endtimestring (icalendar--diarytime-to-isotime
1218 (if (match-beginning 6)
1219 (substring entry-main
1223 (if (match-beginning 7)
1224 (substring entry-main
1228 (summary (icalendar--convert-string-for-export
1229 (substring entry-main
(match-beginning 8)
1231 (icalendar--dmsg "ordinary %s" entry-main
)
1233 (unless startisostring
1234 (error "Could not parse date"))
1236 ;; If only start-date is specified, then end-date is next day,
1237 ;; otherwise it is same day.
1238 (setq endisostring1
(if starttimestring
1242 (when starttimestring
1243 (unless endtimestring
1245 (read (icalendar--rris "^T0?" ""
1248 ;; Case: ends on same day
1249 (setq endtimestring
(format "T%06d"
1251 ;; Case: ends on next day
1252 (setq endtimestring
(format "T%06d"
1254 (setq endisostring1 endisostring
)) )))
1256 (list (concat "\nDTSTART;"
1257 (if starttimestring
"VALUE=DATE-TIME:"
1260 (or starttimestring
"")
1262 (if endtimestring
"VALUE=DATE-TIME:"
1265 (or endtimestring
""))
1270 (defun icalendar-first-weekday-of-year (abbrevweekday year
)
1271 "Find the first ABBREVWEEKDAY in a given YEAR.
1272 Returns day number."
1273 (let* ((day-of-week-jan01 (calendar-day-of-week (list 1 1 year
)))
1275 (- (icalendar--get-weekday-number abbrevweekday
)
1276 day-of-week-jan01
))))
1277 (cond ((<= result
0)
1278 (setq result
(+ result
7)))
1280 (setq result
(- result
7))))
1283 (defun icalendar--convert-weekly-to-ical (nonmarker entry-main
)
1284 "Convert weekly diary entry to icalendar format.
1285 NONMARKER is a regular expression matching the start of non-marking
1286 entries. ENTRY-MAIN is the first line of the diary entry."
1287 (if (and (string-match (concat nonmarker
1289 "\\(\\([0-9][0-9]?:[0-9][0-9]\\)"
1292 "\\([0-9][0-9]?:[0-9][0-9]\\)"
1295 "\\s-*\\(.*?\\) ?$")
1297 (icalendar--get-weekday-abbrev
1298 (substring entry-main
(match-beginning 1)
1300 (let* ((day (icalendar--get-weekday-abbrev
1301 (substring entry-main
(match-beginning 1)
1303 (starttimestring (icalendar--diarytime-to-isotime
1304 (if (match-beginning 3)
1305 (substring entry-main
1309 (if (match-beginning 4)
1310 (substring entry-main
1314 (endtimestring (icalendar--diarytime-to-isotime
1315 (if (match-beginning 6)
1316 (substring entry-main
1320 (if (match-beginning 7)
1321 (substring entry-main
1325 (summary (icalendar--convert-string-for-export
1326 (substring entry-main
(match-beginning 8)
1328 (icalendar--dmsg "weekly %s" entry-main
)
1330 (when starttimestring
1331 (unless endtimestring
1333 (icalendar--rris "^T0?" ""
1335 (setq endtimestring
(format "T%06d"
1337 (list (concat "\nDTSTART;"
1341 ;; Find the first requested weekday of the
1343 (funcall 'format
"%04d%02d%02d"
1344 icalendar-recurring-start-year
1
1345 (icalendar-first-weekday-of-year
1346 day icalendar-recurring-start-year
))
1347 (or starttimestring
"")
1352 (funcall 'format
"%04d%02d%02d"
1353 ;; end is non-inclusive!
1354 icalendar-recurring-start-year
1
1355 (+ (icalendar-first-weekday-of-year
1356 day icalendar-recurring-start-year
)
1357 (if endtimestring
0 1)))
1358 (or endtimestring
"")
1359 "\nRRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY="
1365 (defun icalendar--convert-yearly-to-ical (nonmarker entry-main
)
1366 "Convert yearly diary entry to icalendar format.
1367 NONMARKER is a regular expression matching the start of non-marking
1368 entries. ENTRY-MAIN is the first line of the diary entry."
1369 (if (string-match (concat nonmarker
1370 (if (eq (icalendar--date-style) 'european
)
1371 "\\([0-9]+[0-9]?\\)\\s-+\\([a-z]+\\)\\s-+"
1372 "\\([a-z]+\\)\\s-+\\([0-9]+[0-9]?\\)\\s-+")
1374 "\\(\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
1376 "-\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1378 "\\s-*\\([^0-9]+.*?\\) ?$" ; must not match years
1381 (let* ((daypos (if (eq (icalendar--date-style) 'european
) 1 2))
1382 (monpos (if (eq (icalendar--date-style) 'european
) 2 1))
1383 (day (read (substring entry-main
1384 (match-beginning daypos
)
1385 (match-end daypos
))))
1386 (month (icalendar--get-month-number
1387 (substring entry-main
1388 (match-beginning monpos
)
1389 (match-end monpos
))))
1390 (starttimestring (icalendar--diarytime-to-isotime
1391 (if (match-beginning 4)
1392 (substring entry-main
1396 (if (match-beginning 5)
1397 (substring entry-main
1401 (endtimestring (icalendar--diarytime-to-isotime
1402 (if (match-beginning 7)
1403 (substring entry-main
1407 (if (match-beginning 8)
1408 (substring entry-main
1412 (summary (icalendar--convert-string-for-export
1413 (substring entry-main
(match-beginning 9)
1415 (icalendar--dmsg "yearly %s" entry-main
)
1417 (when starttimestring
1418 (unless endtimestring
1420 (icalendar--rris "^T0?" ""
1422 (setq endtimestring
(format "T%06d"
1424 (list (concat "\nDTSTART;"
1425 (if starttimestring
"VALUE=DATE-TIME:"
1427 (format "1900%02d%02d" month day
)
1428 (or starttimestring
"")
1430 (if endtimestring
"VALUE=DATE-TIME:"
1432 ;; end is not included! shift by one day
1433 (icalendar--date-to-isodate
1434 (list month day
1900)
1435 (if endtimestring
0 1))
1436 (or endtimestring
"")
1437 "\nRRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH="
1445 (defun icalendar--convert-sexp-to-ical (nonmarker entry-main
)
1446 "Convert complex sexp diary entry to icalendar format -- unsupported!
1450 NONMARKER is a regular expression matching the start of non-marking
1451 entries. ENTRY-MAIN is the first line of the diary entry."
1452 (cond ((string-match (concat nonmarker
1453 "%%(and \\(([^)]+)\\))\\(\\s-*.*?\\) ?$")
1455 ;; simple sexp entry as generated by icalendar.el: strip off the
1456 ;; unnecessary (and)
1457 (icalendar--dmsg "diary-sexp from icalendar.el %s" entry-main
)
1458 (icalendar--convert-to-ical
1461 (substring entry-main
(match-beginning 1) (match-end 1))
1462 (substring entry-main
(match-beginning 2) (match-end 2)))))
1463 ((string-match (concat nonmarker
1466 (icalendar--dmsg "diary-sexp %s" entry-main
)
1467 (error "Sexp-entries are not supported yet"))
1472 (defun icalendar--convert-block-to-ical (nonmarker entry-main
)
1473 "Convert block diary entry to icalendar format.
1474 NONMARKER is a regular expression matching the start of non-marking
1475 entries. ENTRY-MAIN is the first line of the diary entry."
1476 (if (string-match (concat nonmarker
1477 "%%(diary-block \\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\)"
1478 " +\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\))\\s-*"
1479 "\\(\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
1481 "-\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1483 "\\s-*\\(.*?\\) ?$")
1485 (let* ((startstring (substring entry-main
1488 (endstring (substring entry-main
1491 (startisostring (icalendar--datestring-to-isodate
1493 (endisostring (icalendar--datestring-to-isodate
1495 (endisostring+1 (icalendar--datestring-to-isodate
1497 (starttimestring (icalendar--diarytime-to-isotime
1498 (if (match-beginning 4)
1499 (substring entry-main
1503 (if (match-beginning 5)
1504 (substring entry-main
1508 (endtimestring (icalendar--diarytime-to-isotime
1509 (if (match-beginning 7)
1510 (substring entry-main
1514 (if (match-beginning 8)
1515 (substring entry-main
1519 (summary (icalendar--convert-string-for-export
1520 (substring entry-main
(match-beginning 9)
1522 (icalendar--dmsg "diary-block %s" entry-main
)
1523 (when starttimestring
1524 (unless endtimestring
1526 (read (icalendar--rris "^T0?" ""
1528 (setq endtimestring
(format "T%06d"
1531 ;; with time -> write rrule
1532 (list (concat "\nDTSTART;VALUE=DATE-TIME:"
1535 "\nDTEND;VALUE=DATE-TIME:"
1538 "\nRRULE:FREQ=DAILY;INTERVAL=1;UNTIL="
1541 ;; no time -> write long event
1542 (list (concat "\nDTSTART;VALUE=DATE:" startisostring
1543 "\nDTEND;VALUE=DATE:" endisostring
+1)
1548 (defun icalendar--convert-float-to-ical (nonmarker entry-main
)
1549 "Convert float diary entry to icalendar format -- unsupported!
1553 NONMARKER is a regular expression matching the start of non-marking
1554 entries. ENTRY-MAIN is the first line of the diary entry."
1555 (if (string-match (concat nonmarker
1556 "%%(diary-float \\([^)]+\\))\\s-*\\(.*?\\) ?$")
1559 (icalendar--dmsg "diary-float %s" entry-main
)
1560 (error "`diary-float' is not supported yet"))
1564 (defun icalendar--convert-date-to-ical (nonmarker entry-main
)
1565 "Convert `diary-date' diary entry to icalendar format -- unsupported!
1569 NONMARKER is a regular expression matching the start of non-marking
1570 entries. ENTRY-MAIN is the first line of the diary entry."
1571 (if (string-match (concat nonmarker
1572 "%%(diary-date \\([^)]+\\))\\s-*\\(.*?\\) ?$")
1575 (icalendar--dmsg "diary-date %s" entry-main
)
1576 (error "`diary-date' is not supported yet"))
1580 (defun icalendar--convert-cyclic-to-ical (nonmarker entry-main
)
1581 "Convert `diary-cyclic' diary entry to icalendar format.
1582 NONMARKER is a regular expression matching the start of non-marking
1583 entries. ENTRY-MAIN is the first line of the diary entry."
1584 (if (string-match (concat nonmarker
1585 "%%(diary-cyclic \\([^ ]+\\) +"
1586 "\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\))\\s-*"
1587 "\\(\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
1589 "-\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1591 "\\s-*\\(.*?\\) ?$")
1593 (let* ((frequency (substring entry-main
(match-beginning 1)
1595 (datetime (substring entry-main
(match-beginning 2)
1597 (startisostring (icalendar--datestring-to-isodate
1599 (endisostring (icalendar--datestring-to-isodate
1601 (endisostring+1 (icalendar--datestring-to-isodate
1603 (starttimestring (icalendar--diarytime-to-isotime
1604 (if (match-beginning 4)
1605 (substring entry-main
1609 (if (match-beginning 5)
1610 (substring entry-main
1614 (endtimestring (icalendar--diarytime-to-isotime
1615 (if (match-beginning 7)
1616 (substring entry-main
1620 (if (match-beginning 8)
1621 (substring entry-main
1625 (summary (icalendar--convert-string-for-export
1626 (substring entry-main
(match-beginning 9)
1628 (icalendar--dmsg "diary-cyclic %s" entry-main
)
1629 (when starttimestring
1630 (unless endtimestring
1632 (read (icalendar--rris "^T0?" ""
1634 (setq endtimestring
(format "T%06d"
1636 (list (concat "\nDTSTART;"
1637 (if starttimestring
"VALUE=DATE-TIME:"
1640 (or starttimestring
"")
1642 (if endtimestring
"VALUE=DATE-TIME:"
1644 (if endtimestring endisostring endisostring
+1)
1645 (or endtimestring
"")
1646 "\nRRULE:FREQ=DAILY;INTERVAL=" frequency
1647 ;; strange: korganizer does not expect
1648 ;; BYSOMETHING here...
1654 (defun icalendar--convert-anniversary-to-ical (nonmarker entry-main
)
1655 "Convert `diary-anniversary' diary entry to icalendar format.
1656 NONMARKER is a regular expression matching the start of non-marking
1657 entries. ENTRY-MAIN is the first line of the diary entry."
1658 (if (string-match (concat nonmarker
1659 "%%(diary-anniversary \\([^)]+\\))\\s-*"
1660 "\\(\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
1662 "-\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1664 "\\s-*\\(.*?\\) ?$")
1666 (let* ((datetime (substring entry-main
(match-beginning 1)
1668 (startisostring (icalendar--datestring-to-isodate
1670 (endisostring (icalendar--datestring-to-isodate
1672 (starttimestring (icalendar--diarytime-to-isotime
1673 (if (match-beginning 3)
1674 (substring entry-main
1678 (if (match-beginning 4)
1679 (substring entry-main
1683 (endtimestring (icalendar--diarytime-to-isotime
1684 (if (match-beginning 6)
1685 (substring entry-main
1689 (if (match-beginning 7)
1690 (substring entry-main
1694 (summary (icalendar--convert-string-for-export
1695 (substring entry-main
(match-beginning 8)
1697 (icalendar--dmsg "diary-anniversary %s" entry-main
)
1698 (when starttimestring
1699 (unless endtimestring
1701 (read (icalendar--rris "^T0?" ""
1703 (setq endtimestring
(format "T%06d"
1705 (list (concat "\nDTSTART;"
1706 (if starttimestring
"VALUE=DATE-TIME:"
1709 (or starttimestring
"")
1711 (if endtimestring
"VALUE=DATE-TIME:"
1714 (or endtimestring
"")
1715 "\nRRULE:FREQ=YEARLY;INTERVAL=1"
1716 ;; the following is redundant,
1717 ;; but korganizer seems to expect this... ;(
1718 ;; and evolution doesn't understand it... :(
1719 ;; so... who is wrong?!
1721 (substring startisostring
4 6)
1723 (substring startisostring
6 8))
1728 ;; ======================================================================
1729 ;; Import -- convert icalendar to emacs-diary
1730 ;; ======================================================================
1733 (defun icalendar-import-file (ical-filename diary-filename
1734 &optional non-marking
)
1735 "Import an iCalendar file and append to a diary file.
1736 Argument ICAL-FILENAME output iCalendar file.
1737 Argument DIARY-FILENAME input `diary-file'.
1738 Optional argument NON-MARKING determines whether events are created as
1739 non-marking or not."
1740 (interactive "fImport iCalendar data from file:
1743 ;; clean up the diary file
1744 (save-current-buffer
1745 ;; now load and convert from the ical file
1746 (set-buffer (find-file ical-filename
))
1747 (icalendar-import-buffer diary-filename t non-marking
)))
1750 (defun icalendar-import-buffer (&optional diary-file do-not-ask
1752 "Extract iCalendar events from current buffer.
1754 This function searches the current buffer for the first iCalendar
1755 object, reads it and adds all VEVENT elements to the diary
1758 It will ask for each appointment whether to add it to the diary
1759 unless DO-NOT-ASK is non-nil. When called interactively,
1760 DO-NOT-ASK is nil, so that you are asked for each event.
1762 NON-MARKING determines whether diary events are created as
1765 Return code t means that importing worked well, return code nil
1766 means that an error has occurred. Error messages will be in the
1767 buffer `*icalendar-errors*'."
1769 (save-current-buffer
1771 (message "Preparing icalendar...")
1772 (set-buffer (icalendar--get-unfolded-buffer (current-buffer)))
1773 (goto-char (point-min))
1774 (message "Preparing icalendar...done")
1775 (if (re-search-forward "^BEGIN:VCALENDAR\\s-*$" nil t
)
1776 (let (ical-contents ical-errors
)
1778 (message "Reading icalendar...")
1780 (setq ical-contents
(icalendar--read-element nil nil
))
1781 (message "Reading icalendar...done")
1783 (message "Converting icalendar...")
1784 (setq ical-errors
(icalendar--convert-ical-to-diary
1786 diary-file do-not-ask non-marking
))
1788 ;; save the diary file if it is visited already
1789 (let ((b (find-buffer-visiting diary-file
)))
1791 (save-current-buffer
1794 (message "Converting icalendar...done")
1795 ;; return t if no error occurred
1798 "Current buffer does not contain icalendar contents!")
1799 ;; return nil, i.e. import did not work
1802 (defalias 'icalendar-extract-ical-from-buffer
'icalendar-import-buffer
)
1803 (make-obsolete 'icalendar-extract-ical-from-buffer
'icalendar-import-buffer
"22.1")
1805 (defun icalendar--format-ical-event (event)
1806 "Create a string representation of an iCalendar EVENT."
1807 (if (functionp icalendar-import-format
)
1808 (funcall icalendar-import-format event
)
1809 (let ((string icalendar-import-format
)
1811 '(("%c" CLASS icalendar-import-format-class
)
1812 ("%d" DESCRIPTION icalendar-import-format-description
)
1813 ("%l" LOCATION icalendar-import-format-location
)
1814 ("%o" ORGANIZER icalendar-import-format-organizer
)
1815 ("%s" SUMMARY icalendar-import-format-summary
)
1816 ("%t" STATUS icalendar-import-format-status
)
1817 ("%u" URL icalendar-import-format-url
))))
1818 ;; convert the specifiers in the format string
1820 (let* ((spec (car i
))
1822 (format (car (cddr i
)))
1823 (contents (icalendar--get-event-property event prop
))
1824 (formatted-contents ""))
1825 (when (and contents
(> (length contents
) 0))
1826 (setq formatted-contents
1827 (icalendar--rris "%s"
1828 (icalendar--convert-string-for-import
1830 (symbol-value format
)
1832 (setq string
(icalendar--rris spec
1839 (defun icalendar--convert-ical-to-diary (ical-list diary-file
1840 &optional do-not-ask
1842 "Convert iCalendar data to an Emacs diary file.
1843 Import VEVENTS from the iCalendar object ICAL-LIST and saves them to a
1844 DIARY-FILE. If DO-NOT-ASK is nil the user is asked for each event
1845 whether to actually import it. NON-MARKING determines whether diary
1846 events are created as non-marking.
1847 This function attempts to return t if something goes wrong. In this
1848 case an error string which describes all the errors and problems is
1849 written into the buffer `*icalendar-errors*'."
1850 (let* ((ev (icalendar--all-events ical-list
))
1854 (zone-map (icalendar--convert-all-timezones ical-list
))
1856 ;; step through all events/appointments
1861 (condition-case error-val
1862 (let* ((dtstart (icalendar--get-event-property e
'DTSTART
))
1863 (dtstart-zone (icalendar--find-time-zone
1864 (icalendar--get-event-property-attributes
1867 (dtstart-dec (icalendar--decode-isodatetime dtstart nil
1869 (start-d (icalendar--datetime-to-diary-date
1871 (start-t (icalendar--datetime-to-colontime dtstart-dec
))
1872 (dtend (icalendar--get-event-property e
'DTEND
))
1873 (dtend-zone (icalendar--find-time-zone
1874 (icalendar--get-event-property-attributes
1877 (dtend-dec (icalendar--decode-isodatetime dtend
1879 (dtend-1-dec (icalendar--decode-isodatetime dtend -
1
1884 (summary (icalendar--convert-string-for-import
1885 (or (icalendar--get-event-property e
'SUMMARY
)
1887 (rrule (icalendar--get-event-property e
'RRULE
))
1888 (rdate (icalendar--get-event-property e
'RDATE
))
1889 (duration (icalendar--get-event-property e
'DURATION
)))
1890 (icalendar--dmsg "%s: `%s'" start-d summary
)
1891 ;; check whether start-time is missing
1894 (cadr (icalendar--get-event-property-attributes
1899 (let ((dtend-dec-d (icalendar--add-decoded-times
1901 (icalendar--decode-isoduration duration
)))
1902 (dtend-1-dec-d (icalendar--add-decoded-times
1904 (icalendar--decode-isoduration duration
1906 (if (and dtend-dec
(not (eq dtend-dec dtend-dec-d
)))
1907 (message "Inconsistent endtime and duration for %s"
1909 (setq dtend-dec dtend-dec-d
)
1910 (setq dtend-1-dec dtend-1-dec-d
)))
1911 (setq end-d
(if dtend-dec
1912 (icalendar--datetime-to-diary-date dtend-dec
)
1914 (setq end-1-d
(if dtend-1-dec
1915 (icalendar--datetime-to-diary-date dtend-1-dec
)
1917 (setq end-t
(if (and
1921 (icalendar--get-event-property-attributes
1924 (icalendar--datetime-to-colontime dtend-dec
)
1926 (icalendar--dmsg "start-d: %s, end-d: %s" start-d end-d
)
1931 (icalendar--convert-recurring-to-diary e dtstart-dec start-t
1935 (icalendar--dmsg "rdate event")
1936 (setq diary-string
"")
1937 (mapc (lambda (datestring)
1939 (concat diary-string
1940 (format "......"))))
1941 (icalendar--split-value rdate
)))
1942 ;; non-recurring event
1944 ((not (string= start-d end-d
))
1946 (icalendar--convert-non-recurring-all-day-to-diary
1950 ((and start-t
(or (not end-t
)
1951 (not (string= start-t end-t
))))
1953 (icalendar--convert-non-recurring-not-all-day-to-diary
1954 e dtstart-dec dtend-dec start-t end-t
))
1958 (icalendar--dmsg "all day event")
1959 (setq diary-string
(icalendar--datetime-to-diary-date
1962 ;; add all other elements unless the user doesn't want to have
1967 (concat diary-string
" "
1968 (icalendar--format-ical-event e
)))
1969 (if do-not-ask
(setq summary nil
))
1970 ;; add entry to diary and store actual name of diary
1971 ;; file (in case it was nil)
1973 (icalendar--add-diary-entry diary-string diary-file
1974 non-marking summary
)))
1976 (setq found-error t
)
1978 (format "%s\nCannot handle this event:%s"
1980 ;; FIXME: inform user about ignored event properties
1983 (message "Ignoring event \"%s\"" e
)
1984 (setq found-error t
)
1985 (setq error-string
(format "%s\n%s\nCannot handle this event: %s"
1986 error-val error-string e
))
1987 (message "%s" error-string
))))
1989 ;; insert final newline
1991 (let ((b (find-buffer-visiting diary-file
)))
1993 (save-current-buffer
1995 (goto-char (point-max))
1998 (save-current-buffer
1999 (set-buffer (get-buffer-create "*icalendar-errors*"))
2001 (insert error-string
)))
2002 (message "Converting icalendar...done")
2005 ;; subroutines for importing
2006 (defun icalendar--convert-recurring-to-diary (e dtstart-dec start-t end-t
)
2007 "Convert recurring icalendar event E to diary format.
2009 DTSTART-DEC is the DTSTART property of E.
2010 START-T is the event's start time in diary format.
2011 END-T is the event's end time in diary format."
2012 (icalendar--dmsg "recurring event")
2013 (let* ((rrule (icalendar--get-event-property e
'RRULE
))
2014 (rrule-props (icalendar--split-value rrule
))
2015 (frequency (cadr (assoc 'FREQ rrule-props
)))
2016 (until (cadr (assoc 'UNTIL rrule-props
)))
2017 (count (cadr (assoc 'COUNT rrule-props
)))
2018 (interval (read (or (cadr (assoc 'INTERVAL rrule-props
)) "1")))
2019 (dtstart-conv (icalendar--datetime-to-diary-date dtstart-dec
))
2020 (until-conv (icalendar--datetime-to-diary-date
2021 (icalendar--decode-isodatetime until
)))
2022 (until-1-conv (icalendar--datetime-to-diary-date
2023 (icalendar--decode-isodatetime until -
1)))
2026 ;; FIXME FIXME interval!!!!!!!!!!!!!
2030 (message "Must not have UNTIL and COUNT -- ignoring COUNT element!")
2032 (cond ((string-equal frequency
"DAILY")
2033 (setq until
(icalendar--add-decoded-times
2035 (list 0 0 0 (* (read count
) interval
) 0 0)))
2036 (setq until-1
(icalendar--add-decoded-times
2038 (list 0 0 0 (* (- (read count
) 1) interval
)
2041 ((string-equal frequency
"WEEKLY")
2042 (setq until
(icalendar--add-decoded-times
2044 (list 0 0 0 (* (read count
) 7 interval
) 0 0)))
2045 (setq until-1
(icalendar--add-decoded-times
2047 (list 0 0 0 (* (- (read count
) 1) 7
2050 ((string-equal frequency
"MONTHLY")
2051 (setq until
(icalendar--add-decoded-times
2052 dtstart-dec
(list 0 0 0 0 (* (- (read count
) 1)
2054 (setq until-1
(icalendar--add-decoded-times
2055 dtstart-dec
(list 0 0 0 0 (* (- (read count
) 1)
2058 ((string-equal frequency
"YEARLY")
2059 (setq until
(icalendar--add-decoded-times
2060 dtstart-dec
(list 0 0 0 0 0 (* (- (read count
) 1)
2062 (setq until-1
(icalendar--add-decoded-times
2064 (list 0 0 0 0 0 (* (- (read count
) 1)
2068 (message "Cannot handle COUNT attribute for `%s' events."
2070 (setq until-conv
(icalendar--datetime-to-diary-date until
))
2071 (setq until-1-conv
(icalendar--datetime-to-diary-date until-1
))
2074 (cond ((string-equal frequency
"WEEKLY")
2075 (let* ((byday (cadr (assoc 'BYDAY rrule-props
)))
2077 (icalendar--get-weekday-numbers byday
))
2079 (when (> (length weekdays
) 1)
2080 (format "(memq (calendar-day-of-week date) '%s) "
2084 ;; weekly and all-day
2085 (icalendar--dmsg "weekly all-day")
2091 "(diary-block %s %s))")
2093 (format "(diary-cyclic %d %s) "
2097 (if count until-1-conv until-conv
)
2100 (format "%%%%(and %s(diary-cyclic %d %s))"
2101 (or weekday-clause
"")
2102 (if weekday-clause
1 (* interval
7))
2104 ;; weekly and not all-day
2105 (icalendar--dmsg "weekly not-all-day")
2111 "(diary-block %s %s)) "
2114 (format "(diary-cyclic %d %s) "
2120 (if end-t
"-" "") (or end-t
"")))
2123 ;; DTSTART;VALUE=DATE-TIME:20030919T090000
2124 ;; DTEND;VALUE=DATE-TIME:20030919T113000
2127 "%%%%(and %s(diary-cyclic %d %s)) %s%s%s"
2128 (or weekday-clause
"")
2129 (if weekday-clause
1 (* interval
7))
2132 (if end-t
"-" "") (or end-t
"")))))))
2134 ((string-equal frequency
"YEARLY")
2135 (icalendar--dmsg "yearly")
2137 (let ((day (nth 3 dtstart-dec
))
2138 (month (nth 4 dtstart-dec
)))
2139 (setq result
(concat "%%(and (diary-date "
2140 (cond ((eq (icalendar--date-style) 'iso
)
2141 (format "t %d %d" month day
))
2142 ((eq (icalendar--date-style) 'european
)
2143 (format "%d %d t" day month
))
2144 ((eq (icalendar--date-style) 'american
)
2145 (format "%d %d t" month day
)))
2154 (setq result
(format
2155 "%%%%(and (diary-anniversary %s)) %s%s%s"
2158 (if end-t
"-" "") (or end-t
"")))))
2160 ((string-equal frequency
"MONTHLY")
2161 (icalendar--dmsg "monthly")
2164 "%%%%(and (diary-date %s) (diary-block %s %s)) %s%s%s"
2165 (let ((day (nth 3 dtstart-dec
)))
2166 (cond ((eq (icalendar--date-style) 'iso
)
2167 (format "t t %d" day
))
2168 ((eq (icalendar--date-style) 'european
)
2169 (format "%d t t" day
))
2170 ((eq (icalendar--date-style) 'american
)
2171 (format "t %d t" day
))))
2175 (if (eq (icalendar--date-style) 'iso
) "9999 1 1" "1 1 9999")) ;; FIXME: should be unlimited
2177 (if end-t
"-" "") (or end-t
""))))
2179 ((and (string-equal frequency
"DAILY"))
2183 (concat "%%%%(and (diary-cyclic %s %s) "
2184 "(diary-block %s %s)) %s%s%s")
2185 interval dtstart-conv dtstart-conv
2186 (if count until-1-conv until-conv
)
2188 (if end-t
"-" "") (or end-t
"")))
2191 "%%%%(and (diary-cyclic %s %s)) %s%s%s"
2195 (if end-t
"-" "") (or end-t
""))))))
2196 ;; Handle exceptions from recurrence rules
2197 (let ((ex-dates (icalendar--get-event-properties e
'EXDATE
)))
2199 (let* ((ex-start (icalendar--decode-isodatetime
2201 (ex-d (icalendar--datetime-to-diary-date
2204 (icalendar--rris "^%%(\\(and \\)?"
2206 "%%%%(and (not (diary-date %s)) "
2209 (setq ex-dates
(cdr ex-dates
))))
2210 ;; FIXME: exception rules are not recognized
2211 (if (icalendar--get-event-property e
'EXRULE
)
2214 "\n Exception rules: "
2215 (icalendar--get-event-properties
2219 (defun icalendar--convert-non-recurring-all-day-to-diary (event start-d end-d
)
2220 "Convert non-recurring icalendar EVENT to diary format.
2222 DTSTART is the decoded DTSTART property of E.
2223 Argument START-D gives the first day.
2224 Argument END-D gives the last day."
2225 (icalendar--dmsg "non-recurring all-day event")
2226 (format "%%%%(and (diary-block %s %s))" start-d end-d
))
2228 (defun icalendar--convert-non-recurring-not-all-day-to-diary (event dtstart-dec
2232 "Convert recurring icalendar EVENT to diary format.
2234 DTSTART-DEC is the decoded DTSTART property of E.
2235 DTEND-DEC is the decoded DTEND property of E.
2236 START-T is the event's start time in diary format.
2237 END-T is the event's end time in diary format."
2238 (icalendar--dmsg "not all day event")
2241 (icalendar--datetime-to-diary-date
2246 (icalendar--datetime-to-diary-date
2250 (defun icalendar--add-diary-entry (string diary-file non-marking
2252 "Add STRING to the diary file DIARY-FILE.
2253 STRING must be a properly formatted valid diary entry. NON-MARKING
2254 determines whether diary events are created as non-marking. If
2255 SUMMARY is not nil it must be a string that gives the summary of the
2256 entry. In this case the user will be asked whether he wants to insert
2258 (when (or (not summary
)
2259 (y-or-n-p (format "Add appointment for `%s' to diary? "
2263 (y-or-n-p (format "Make appointment non-marking? "))))
2264 (save-window-excursion
2267 (read-file-name "Add appointment to this diary file: ")))
2268 ;; Note: diary-make-entry will add a trailing blank char.... :(
2269 (funcall (if (fboundp 'diary-make-entry
)
2272 string non-marking diary-file
)))
2273 ;; Würgaround to remove the trailing blank char
2274 (with-current-buffer (find-file diary-file
)
2275 (goto-char (point-max))
2276 (if (= (char-before) ?
)
2278 ;; return diary-file in case it has been changed interactively
2281 ;; ======================================================================
2283 ;; ======================================================================
2284 (defun icalendar-import-format-sample (event)
2285 "Example function for formatting an icalendar EVENT."
2286 (format (concat "SUMMARY=`%s' DESCRIPTION=`%s' LOCATION=`%s' ORGANIZER=`%s' "
2287 "STATUS=`%s' URL=`%s' CLASS=`%s'")
2288 (or (icalendar--get-event-property event
'SUMMARY
) "")
2289 (or (icalendar--get-event-property event
'DESCRIPTION
) "")
2290 (or (icalendar--get-event-property event
'LOCATION
) "")
2291 (or (icalendar--get-event-property event
'ORGANIZER
) "")
2292 (or (icalendar--get-event-property event
'STATUS
) "")
2293 (or (icalendar--get-event-property event
'URL
) "")
2294 (or (icalendar--get-event-property event
'CLASS
) "")))
2296 (provide 'icalendar
)
2298 ;; arch-tag: 74fdbe8e-0451-4e38-bb61-4416e822f4fc
2299 ;;; icalendar.el ends here