comparison lisp/calendar/icalendar.el @ 62053:57aabea77432

From Ulf Jasper <ulf.jasper@web.de>: (icalendar-version): Now at 0.12. (icalendar-duration-correction): Remove. (icalendar--get-event-properties): Split result at commas. (icalendar--decode-isoduration): New optional argument DURATION-CORRECTION. (icalendar--convert-ordinary-to-ical) (icalendar--convert-weekly-to-ical) (icalendar--convert-yearly-to-ical) (icalendar--convert-sexp-to-ical) (icalendar--convert-block-to-ical) (icalendar--convert-float-to-ical) (icalendar--convert-date-to-ical) (icalendar--convert-cyclic-to-ical) (icalendar--convert-anniversary-to-ical): New functions, extracted from icalendar-export-region, with bug fixes. (icalendar-export-region): Use the above functions. (icalendar-import-buffer): Check before saving diary file. (icalendar--convert-recurring-to-diary) (icalendar--convert-non-recurring-all-day-to-diary) (icalendar--convert-non-recurring-not-all-day-to-diary): New functions, extracted from icalendar--convert-ical-to-diary, with bug fixes. (icalendar--convert-ical-to-diary): Use the above functions.
author Glenn Morris <rgm@gnu.org>
date Tue, 03 May 2005 16:35:27 +0000
parents 87759c244379
children 6812f1044f86
comparison
equal deleted inserted replaced
62052:fa7d4d214722 62053:57aabea77432
26 26
27 ;;; Commentary: 27 ;;; Commentary:
28 28
29 ;; This package is documented in the Emacs Manual. 29 ;; This package is documented in the Emacs Manual.
30 30
31 ;; Please note:
32 ;; - Diary entries which have a start time but no end time are assumed to
33 ;; last for one hour when they are exported.
34 ;; - Weekly diary entries are assumed to occur the first time in the first
35 ;; week of the year 2000 when they are exported.
36 ;; - Yearly diary entries are assumed to occur the first time in the year
37 ;; 1900 when they are exported.
31 38
32 ;;; History: 39 ;;; History:
33 40
34 ;; 0.07 onwards: see lisp/ChangeLog 41 ;; 0.07 onwards: see lisp/ChangeLog
35 42
73 ;; + alarm 80 ;; + alarm
74 ;; + exceptions in recurring events 81 ;; + exceptions in recurring events
75 ;; + the parser is too soft 82 ;; + the parser is too soft
76 ;; + error log is incomplete 83 ;; + error log is incomplete
77 ;; + nice to have: #include "webcal://foo.com/some-calendar.ics" 84 ;; + nice to have: #include "webcal://foo.com/some-calendar.ics"
85 ;; + timezones, currently all times are local!
78 86
79 ;; * Export from diary to ical 87 ;; * Export from diary to ical
80 ;; + diary-date, diary-float, and self-made sexp entries are not 88 ;; + diary-date, diary-float, and self-made sexp entries are not
81 ;; understood 89 ;; understood
82 ;; + timezones, currently all times are local!
83 90
84 ;; * Other things 91 ;; * Other things
85 ;; + clean up all those date/time parsing functions 92 ;; + clean up all those date/time parsing functions
86 ;; + Handle todo items? 93 ;; + Handle todo items?
87 ;; + Check iso 8601 for datetime and period 94 ;; + Check iso 8601 for datetime and period
88 ;; + Which chars to (un)escape? 95 ;; + Which chars to (un)escape?
89 96
90 97
91 ;;; Code: 98 ;;; Code:
92 99
93 (defconst icalendar-version 0.11 100 (defconst icalendar-version 0.12
94 "Version number of icalendar.el.") 101 "Version number of icalendar.el.")
95 102
96 ;; ====================================================================== 103 ;; ======================================================================
97 ;; Customizables 104 ;; Customizables
98 ;; ====================================================================== 105 ;; ======================================================================
143 This applies only if the organizer is not empty! `%s' is 150 This applies only if the organizer is not empty! `%s' is
144 replaced by the organizer." 151 replaced by the organizer."
145 :type 'string 152 :type 'string
146 :group 'icalendar) 153 :group 'icalendar)
147 154
148 (defcustom icalendar-duration-correction 155 (defvar icalendar-debug nil
149 t 156 "Enable icalendar debug messages.")
150 "Workaround for all-day events.
151 If non-nil the length=duration of iCalendar appointments that
152 have a length of exactly n days is decreased by one day. This
153 fixes problems with all-day events, which appear to be one day
154 longer than they are."
155 :type 'boolean
156 :group 'icalendar)
157
158 157
159 ;; ====================================================================== 158 ;; ======================================================================
160 ;; NO USER SERVICABLE PARTS BELOW THIS LINE 159 ;; NO USER SERVICABLE PARTS BELOW THIS LINE
161 ;; ====================================================================== 160 ;; ======================================================================
162 161
163 (defconst icalendar--weekday-array ["SU" "MO" "TU" "WE" "TH" "FR" "SA"]) 162 (defconst icalendar--weekday-array ["SU" "MO" "TU" "WE" "TH" "FR" "SA"])
164
165 (defvar icalendar-debug nil ".")
166 163
167 ;; ====================================================================== 164 ;; ======================================================================
168 ;; all the other libs we need 165 ;; all the other libs we need
169 ;; ====================================================================== 166 ;; ======================================================================
170 (require 'calendar) 167 (require 'calendar)
293 "For the given EVENT return a list of all values of the property PROP." 290 "For the given EVENT return a list of all values of the property PROP."
294 (let ((props (car (cddr event))) pp result) 291 (let ((props (car (cddr event))) pp result)
295 (while props 292 (while props
296 (setq pp (car props)) 293 (setq pp (car props))
297 (if (eq (car pp) prop) 294 (if (eq (car pp) prop)
298 (setq result (cons (car (cddr pp)) result))) 295 (setq result (append (split-string (car (cddr pp)) ",") result)))
299 (setq props (cdr props))) 296 (setq props (cdr props)))
300 result)) 297 result))
301 298
302 ;; (defun icalendar--set-event-property (event prop new-value) 299 ;; (defun icalendar--set-event-property (event prop new-value)
303 ;; "For the given EVENT set the property PROP to the value NEW-VALUE." 300 ;; "For the given EVENT set the property PROP to the value NEW-VALUE."
409 ;; hope for the best... 406 ;; hope for the best...
410 (list second minute hour day month year 0 nil 0)))) 407 (list second minute hour day month year 0 nil 0))))
411 ;; isodatetimestring == nil 408 ;; isodatetimestring == nil
412 nil)) 409 nil))
413 410
414 (defun icalendar--decode-isoduration (isodurationstring) 411 (defun icalendar--decode-isoduration (isodurationstring
415 "Return ISODURATIONSTRING in format like `decode-time'. 412 &optional duration-correction)
413 "Convert ISODURATIONSTRING into format provided by `decode-time'.
416 Converts from ISO-8601 to Emacs representation. If ISODURATIONSTRING 414 Converts from ISO-8601 to Emacs representation. If ISODURATIONSTRING
417 specifies UTC time (trailing letter Z) the decoded time is given in 415 specifies UTC time (trailing letter Z) the decoded time is given in
418 the local time zone! 416 the local time zone!
417
418 Optional argument DURATION-CORRECTION shortens result by one day.
419 419
420 FIXME: TZID-attributes are ignored....! 420 FIXME: TZID-attributes are ignored....!
421 FIXME: multiple comma-separated values should be allowed!" 421 FIXME: multiple comma-separated values should be allowed!"
422 (if isodurationstring 422 (if isodurationstring
423 (save-match-data 423 (save-match-data
440 (cond 440 (cond
441 ((match-beginning 2) ;days only 441 ((match-beginning 2) ;days only
442 (setq days (read (substring isodurationstring 442 (setq days (read (substring isodurationstring
443 (match-beginning 3) 443 (match-beginning 3)
444 (match-end 3)))) 444 (match-end 3))))
445 (when icalendar-duration-correction 445 (when duration-correction
446 (setq days (1- days)))) 446 (setq days (1- days))))
447 ((match-beginning 4) ;days and time 447 ((match-beginning 4) ;days and time
448 (if (match-beginning 5) 448 (if (match-beginning 5)
449 (setq days (* 7 (read (substring isodurationstring 449 (setq days (* 7 (read (substring isodurationstring
450 (match-beginning 6) 450 (match-beginning 6)
708 (found-error nil) 708 (found-error nil)
709 (nonmarker (concat "^" (regexp-quote diary-nonmarking-symbol) 709 (nonmarker (concat "^" (regexp-quote diary-nonmarking-symbol)
710 "?"))) 710 "?")))
711 ;; prepare buffer with error messages 711 ;; prepare buffer with error messages
712 (save-current-buffer 712 (save-current-buffer
713 (set-buffer (get-buffer-create " *icalendar-errors*")) 713 (set-buffer (get-buffer-create "*icalendar-errors*"))
714 (erase-buffer)) 714 (erase-buffer))
715 715
716 ;; here we go 716 ;; here we go
717 (save-excursion 717 (save-excursion
718 (goto-char min) 718 (goto-char min)
719 (while (re-search-forward 719 (while (re-search-forward
720 "^\\([^ \t\n].*\\)\\(\\(\n[ \t].*\\)*\\)" max t) 720 "^\\([^ \t\n].+\\)\\(\\(\n[ \t].*\\)*\\)" max t)
721 (setq entry-main (match-string 1)) 721 (setq entry-main (match-string 1))
722 (if (match-beginning 2) 722 (if (match-beginning 2)
723 (setq entry-rest (match-string 2)) 723 (setq entry-rest (match-string 2))
724 (setq entry-rest "")) 724 (setq entry-rest ""))
725 (setq header (format "\nBEGIN:VEVENT\nUID:emacs%d%d%d" 725 (setq header (format "\nBEGIN:VEVENT\nUID:emacs%d%d%d"
726 (car (current-time)) 726 (car (current-time))
727 (cadr (current-time)) 727 (cadr (current-time))
728 (car (cddr (current-time))))) 728 (car (cddr (current-time)))))
729 (condition-case error-val 729 (condition-case error-val
730 (progn 730 (progn
731 (cond 731 (setq contents
732 ;; anniversaries 732 (or
733 ((string-match 733 ;; anniversaries -- %%(diary-anniversary ...)
734 (concat nonmarker 734 (icalendar--convert-anniversary-to-ical nonmarker
735 "%%(diary-anniversary \\([^)]+\\))\\s-*\\(.*\\)") 735 entry-main)
736 entry-main) 736 ;; cyclic events -- %%(diary-cyclic ...)
737 (icalendar--dmsg "diary-anniversary %s" entry-main) 737 (icalendar--convert-cyclic-to-ical nonmarker entry-main)
738 (let* ((datetime (substring entry-main (match-beginning 1) 738 ;; diary-date -- %%(diary-date ...)
739 (match-end 1))) 739 (icalendar--convert-date-to-ical nonmarker entry-main)
740 (summary (icalendar--convert-string-for-export 740 ;; float events -- %%(diary-float ...)
741 (substring entry-main (match-beginning 2) 741 (icalendar--convert-float-to-ical nonmarker entry-main)
742 (match-end 2)))) 742 ;; block events -- %%(diary-block ...)
743 (startisostring (icalendar--datestring-to-isodate 743 (icalendar--convert-block-to-ical nonmarker entry-main)
744 datetime)) 744 ;; other sexp diary entries
745 (endisostring (icalendar--datestring-to-isodate 745 (icalendar--convert-sexp-to-ical nonmarker entry-main)
746 datetime 1))) 746 ;; weekly by day -- Monday 8:30 Team meeting
747 (setq contents 747 (icalendar--convert-weekly-to-ical nonmarker entry-main)
748 (concat "\nDTSTART;VALUE=DATE:" startisostring 748 ;; yearly by day -- 1 May Tag der Arbeit
749 "\nDTEND;VALUE=DATE:" endisostring 749 (icalendar--convert-yearly-to-ical nonmarker entry-main)
750 "\nSUMMARY:" summary 750 ;; "ordinary" events, start and end time given
751 "\nRRULE:FREQ=YEARLY;INTERVAL=1" 751 ;; 1 Feb 2003 blah
752 ;; the following is redundant, 752 (icalendar--convert-ordinary-to-ical nonmarker entry-main)
753 ;; but korganizer seems to expect this... ;( 753 ;; everything else
754 ;; and evolution doesn't understand it... :( 754 ;; Oops! what's that?
755 ;; so... who is wrong?! 755 (error "Could not parse entry")))
756 ";BYMONTH=" 756 (unless (string= entry-rest "")
757 (substring startisostring 4 6) 757 (setq contents
758 ";BYMONTHDAY=" 758 (concat contents "\nDESCRIPTION:"
759 (substring startisostring 6 8)))) 759 (icalendar--convert-string-for-export
760 (unless (string= entry-rest "") 760 entry-rest))))
761 (setq contents
762 (concat contents "\nDESCRIPTION:"
763 (icalendar--convert-string-for-export
764 entry-rest)))))
765 ;; cyclic events
766 ;; %%(diary-cyclic )
767 ((string-match
768 (concat nonmarker
769 "%%(diary-cyclic \\([^ ]+\\) +"
770 "\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\))\\s-*\\(.*\\)")
771 entry-main)
772 (icalendar--dmsg "diary-cyclic %s" entry-main)
773 (let* ((frequency (substring entry-main (match-beginning 1)
774 (match-end 1)))
775 (datetime (substring entry-main (match-beginning 2)
776 (match-end 2)))
777 (summary (icalendar--convert-string-for-export
778 (substring entry-main (match-beginning 3)
779 (match-end 3))))
780 (startisostring (icalendar--datestring-to-isodate
781 datetime))
782 (endisostring (icalendar--datestring-to-isodate
783 datetime 1)))
784 (setq contents
785 (concat "\nDTSTART;VALUE=DATE:" startisostring
786 "\nDTEND;VALUE=DATE:" endisostring
787 "\nSUMMARY:" summary
788 "\nRRULE:FREQ=DAILY;INTERVAL=" frequency
789 ;; strange: korganizer does not expect
790 ;; BYSOMETHING here...
791 )))
792 (unless (string= entry-rest "")
793 (setq contents
794 (concat contents "\nDESCRIPTION:"
795 (icalendar--convert-string-for-export
796 entry-rest)))))
797 ;; diary-date -- FIXME
798 ((string-match
799 (concat nonmarker
800 "%%(diary-date \\([^)]+\\))\\s-*\\(.*\\)")
801 entry-main)
802 (icalendar--dmsg "diary-date %s" entry-main)
803 (error "`diary-date' is not supported yet"))
804 ;; float events -- FIXME
805 ((string-match
806 (concat nonmarker
807 "%%(diary-float \\([^)]+\\))\\s-*\\(.*\\)")
808 entry-main)
809 (icalendar--dmsg "diary-float %s" entry-main)
810 (error "`diary-float' is not supported yet"))
811 ;; block events
812 ((string-match
813 (concat nonmarker
814 "%%(diary-block \\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\)"
815 " +\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\))\\s-*"
816 "\\(.*\\)")
817 entry-main)
818 (icalendar--dmsg "diary-block %s" entry-main)
819 (let* ((startstring (substring entry-main
820 (match-beginning 1)
821 (match-end 1)))
822 (endstring (substring entry-main
823 (match-beginning 2)
824 (match-end 2)))
825 (summary (icalendar--convert-string-for-export
826 (substring entry-main (match-beginning 3)
827 (match-end 3))))
828 (startisostring (icalendar--datestring-to-isodate
829 startstring))
830 (endisostring (icalendar--datestring-to-isodate
831 endstring 1)))
832 (setq contents
833 (concat "\nDTSTART;VALUE=DATE:" startisostring
834 "\nDTEND;VALUE=DATE:" endisostring
835 "\nSUMMARY:" summary))
836 (unless (string= entry-rest "")
837 (setq contents
838 (concat contents "\nDESCRIPTION:"
839 (icalendar--convert-string-for-export
840 entry-rest))))))
841 ;; other sexp diary entries -- FIXME
842 ((string-match
843 (concat nonmarker
844 "%%(\\([^)]+\\))\\s-*\\(.*\\)")
845 entry-main)
846 (icalendar--dmsg "diary-sexp %s" entry-main)
847 (error "sexp-entries are not supported yet"))
848 ;; weekly by day
849 ;; Monday 8:30 Team meeting
850 ((and (string-match
851 (concat nonmarker
852 "\\([a-z]+\\)\\s-+"
853 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)"
854 "\\([ap]m\\)?"
855 "\\(-0?"
856 "\\([1-9][0-9]?:[0-9][0-9]\\)"
857 "\\([ap]m\\)?\\)?"
858 "\\)?"
859 "\\s-*\\(.*\\)$")
860 entry-main)
861 (icalendar--get-weekday-abbrev
862 (substring entry-main (match-beginning 1)
863 (match-end 1))))
864 (icalendar--dmsg "weekly %s" entry-main)
865 (let* ((day (icalendar--get-weekday-abbrev
866 (substring entry-main (match-beginning 1)
867 (match-end 1))))
868 (starttimestring (icalendar--diarytime-to-isotime
869 (if (match-beginning 3)
870 (substring entry-main
871 (match-beginning 3)
872 (match-end 3))
873 nil)
874 (if (match-beginning 4)
875 (substring entry-main
876 (match-beginning 4)
877 (match-end 4))
878 nil)))
879 (endtimestring (icalendar--diarytime-to-isotime
880 (if (match-beginning 6)
881 (substring entry-main
882 (match-beginning 6)
883 (match-end 6))
884 nil)
885 (if (match-beginning 7)
886 (substring entry-main
887 (match-beginning 7)
888 (match-end 7))
889 nil)))
890 (summary (icalendar--convert-string-for-export
891 (substring entry-main (match-beginning 8)
892 (match-end 8)))))
893 (when starttimestring
894 (unless endtimestring
895 (let ((time (read
896 (icalendar--rris "^T0?" ""
897 starttimestring))))
898 (setq endtimestring (format "T%06d"
899 (+ 10000 time))))))
900 (setq contents
901 (concat "\nDTSTART;"
902 (if starttimestring
903 "VALUE=DATE-TIME:"
904 "VALUE=DATE:")
905 ;; find the correct week day,
906 ;; 1st january 2000 was a saturday
907 (format
908 "200001%02d"
909 (+ (icalendar--get-weekday-number day) 2))
910 (or starttimestring "")
911 "\nDTEND;"
912 (if endtimestring
913 "VALUE=DATE-TIME:"
914 "VALUE=DATE:")
915 (format
916 "200001%02d"
917 ;; end is non-inclusive!
918 (+ (icalendar--get-weekday-number day)
919 (if endtimestring 2 3)))
920 (or endtimestring "")
921 "\nSUMMARY:" summary
922 "\nRRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY="
923 day)))
924 (unless (string= entry-rest "")
925 (setq contents
926 (concat contents "\nDESCRIPTION:"
927 (icalendar--convert-string-for-export
928 entry-rest)))))
929 ;; yearly by day
930 ;; 1 May Tag der Arbeit
931 ((string-match
932 (concat nonmarker
933 (if european-calendar-style
934 "0?\\([1-9]+[0-9]?\\)\\s-+\\([a-z]+\\)\\s-+"
935 "\\([a-z]+\\)\\s-+0?\\([1-9]+[0-9]?\\)\\s-+")
936 "\\*?\\s-*"
937 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
938 "\\("
939 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
940 "\\)?"
941 "\\s-*\\([^0-9]+.*\\)$" ; must not match years
942 )
943 entry-main)
944 (icalendar--dmsg "yearly %s" entry-main)
945 (let* ((daypos (if european-calendar-style 1 2))
946 (monpos (if european-calendar-style 2 1))
947 (day (read (substring entry-main
948 (match-beginning daypos)
949 (match-end daypos))))
950 (month (icalendar--get-month-number
951 (substring entry-main
952 (match-beginning monpos)
953 (match-end monpos))))
954 (starttimestring (icalendar--diarytime-to-isotime
955 (if (match-beginning 4)
956 (substring entry-main
957 (match-beginning 4)
958 (match-end 4))
959 nil)
960 (if (match-beginning 5)
961 (substring entry-main
962 (match-beginning 5)
963 (match-end 5))
964 nil)))
965 (endtimestring (icalendar--diarytime-to-isotime
966 (if (match-beginning 7)
967 (substring entry-main
968 (match-beginning 7)
969 (match-end 7))
970 nil)
971 (if (match-beginning 8)
972 (substring entry-main
973 (match-beginning 8)
974 (match-end 8))
975 nil)))
976 (summary (icalendar--convert-string-for-export
977 (substring entry-main (match-beginning 9)
978 (match-end 9)))))
979 (when starttimestring
980 (unless endtimestring
981 (let ((time (read
982 (icalendar--rris "^T0?" ""
983 starttimestring))))
984 (setq endtimestring (format "T%06d"
985 (+ 10000 time))))))
986 (setq contents
987 (concat "\nDTSTART;"
988 (if starttimestring "VALUE=DATE-TIME:"
989 "VALUE=DATE:")
990 (format "1900%02d%02d" month day)
991 (or starttimestring "")
992 "\nDTEND;"
993 (if endtimestring "VALUE=DATE-TIME:"
994 "VALUE=DATE:")
995 ;; end is not included! shift by one day
996 (icalendar--date-to-isodate
997 (list month day 1900)
998 (if endtimestring 0 1))
999 (or endtimestring "")
1000 "\nSUMMARY:"
1001 summary
1002 "\nRRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH="
1003 (format "%2d" month)
1004 ";BYMONTHDAY="
1005 (format "%2d" day))))
1006 (unless (string= entry-rest "")
1007 (setq contents
1008 (concat contents "\nDESCRIPTION:"
1009 (icalendar--convert-string-for-export
1010 entry-rest)))))
1011 ;; "ordinary" events, start and end time given
1012 ;; 1 Feb 2003 Hs Hochzeitsfeier, Dreieich
1013 ((string-match
1014 (concat nonmarker
1015 "\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\)\\s-+"
1016 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
1017 "\\("
1018 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1019 "\\)?"
1020 "\\s-*\\(.*\\)")
1021 entry-main)
1022 (icalendar--dmsg "ordinary %s" entry-main)
1023 (let* ((startdatestring (icalendar--datestring-to-isodate
1024 (substring entry-main
1025 (match-beginning 1)
1026 (match-end 1))))
1027 (starttimestring (icalendar--diarytime-to-isotime
1028 (if (match-beginning 3)
1029 (substring entry-main
1030 (match-beginning 3)
1031 (match-end 3))
1032 nil)
1033 (if (match-beginning 4)
1034 (substring entry-main
1035 (match-beginning 4)
1036 (match-end 4))
1037 nil)))
1038 (endtimestring (icalendar--diarytime-to-isotime
1039 (if (match-beginning 6)
1040 (substring entry-main
1041 (match-beginning 6)
1042 (match-end 6))
1043 nil)
1044 (if (match-beginning 7)
1045 (substring entry-main
1046 (match-beginning 7)
1047 (match-end 7))
1048 nil)))
1049 (summary (icalendar--convert-string-for-export
1050 (substring entry-main (match-beginning 8)
1051 (match-end 8)))))
1052 (unless startdatestring
1053 (error "Could not parse date"))
1054 (when starttimestring
1055 (unless endtimestring
1056 (let ((time
1057 (read (icalendar--rris "^T0?" ""
1058 starttimestring))))
1059 (setq endtimestring (format "T%06d"
1060 (+ 10000 time))))))
1061 (setq contents (concat
1062 "\nDTSTART;"
1063 (if starttimestring "VALUE=DATE-TIME:"
1064 "VALUE=DATE:")
1065 startdatestring
1066 (or starttimestring "")
1067 "\nDTEND;"
1068 (if endtimestring "VALUE=DATE-TIME:"
1069 "VALUE=DATE:")
1070 (icalendar--datestring-to-isodate
1071 (substring entry-main
1072 (match-beginning 1)
1073 (match-end 1))
1074 (if endtimestring 0 1))
1075 (or endtimestring "")
1076 "\nSUMMARY:"
1077 summary))
1078 ;; could not parse the date
1079 (unless (string= entry-rest "")
1080 (setq contents
1081 (concat contents "\nDESCRIPTION:"
1082 (icalendar--convert-string-for-export
1083 entry-rest))))))
1084 ;; everything else
1085 (t
1086 ;; Oops! what's that?
1087 (error "Could not parse entry")))
1088 (setq result (concat result header contents "\nEND:VEVENT"))) 761 (setq result (concat result header contents "\nEND:VEVENT")))
1089 ;; handle errors 762 ;; handle errors
1090 (error 763 (error
1091 (setq found-error t) 764 (setq found-error t)
1092 (save-current-buffer 765 (save-current-buffer
1093 (set-buffer (get-buffer-create " *icalendar-errors*")) 766 (set-buffer (get-buffer-create "*icalendar-errors*"))
1094 (insert (format "Error in line %d -- %s: `%s'\n" 767 (insert (format "Error in line %d -- %s: `%s'\n"
1095 (count-lines (point-min) (point)) 768 (count-lines (point-min) (point))
1096 (cadr error-val) 769 (cadr error-val)
1097 entry-main)))))) 770 entry-main))))))
1098 771
1107 (insert result) 780 (insert result)
1108 (insert "\nEND:VCALENDAR\n") 781 (insert "\nEND:VCALENDAR\n")
1109 ;; save the diary file 782 ;; save the diary file
1110 (save-buffer)))) 783 (save-buffer))))
1111 found-error)) 784 found-error))
785
786 ;; subroutines
787 (defun icalendar--convert-ordinary-to-ical (nonmarker entry-main)
788 "Convert \"ordinary\" diary entry to icalendar format.
789
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\\)?"
795 "\\("
796 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
797 "\\)?"
798 "\\s-*\\(.*\\)")
799 entry-main)
800 (let* ((datetime (substring entry-main (match-beginning 1)
801 (match-end 1)))
802 (startisostring (icalendar--datestring-to-isodate
803 datetime))
804 (endisostring (icalendar--datestring-to-isodate
805 datetime 1))
806 (starttimestring (icalendar--diarytime-to-isotime
807 (if (match-beginning 3)
808 (substring entry-main
809 (match-beginning 3)
810 (match-end 3))
811 nil)
812 (if (match-beginning 4)
813 (substring entry-main
814 (match-beginning 4)
815 (match-end 4))
816 nil)))
817 (endtimestring (icalendar--diarytime-to-isotime
818 (if (match-beginning 6)
819 (substring entry-main
820 (match-beginning 6)
821 (match-end 6))
822 nil)
823 (if (match-beginning 7)
824 (substring entry-main
825 (match-beginning 7)
826 (match-end 7))
827 nil)))
828 (summary (icalendar--convert-string-for-export
829 (substring entry-main (match-beginning 8)
830 (match-end 8)))))
831 (icalendar--dmsg "ordinary %s" entry-main)
832
833 (unless startisostring
834 (error "Could not parse date"))
835 (when starttimestring
836 (unless endtimestring
837 (let ((time
838 (read (icalendar--rris "^T0?" ""
839 starttimestring))))
840 (setq endtimestring (format "T%06d"
841 (+ 10000 time))))))
842 (concat "\nDTSTART;"
843 (if starttimestring "VALUE=DATE-TIME:"
844 "VALUE=DATE:")
845 startisostring
846 (or starttimestring "")
847 "\nDTEND;"
848 (if endtimestring "VALUE=DATE-TIME:"
849 "VALUE=DATE:")
850 (if starttimestring
851 startisostring
852 endisostring)
853 (or endtimestring "")
854 "\nSUMMARY:"
855 summary))
856 ;; no match
857 nil))
858
859 (defun icalendar--convert-weekly-to-ical (nonmarker entry-main)
860 "Convert weekly diary entry to icalendar format.
861
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
865 "\\([a-z]+\\)\\s-+"
866 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)"
867 "\\([ap]m\\)?"
868 "\\(-0?"
869 "\\([1-9][0-9]?:[0-9][0-9]\\)"
870 "\\([ap]m\\)?\\)?"
871 "\\)?"
872 "\\s-*\\(.*\\)$")
873 entry-main)
874 (icalendar--get-weekday-abbrev
875 (substring entry-main (match-beginning 1)
876 (match-end 1))))
877 (let* ((day (icalendar--get-weekday-abbrev
878 (substring entry-main (match-beginning 1)
879 (match-end 1))))
880 (starttimestring (icalendar--diarytime-to-isotime
881 (if (match-beginning 3)
882 (substring entry-main
883 (match-beginning 3)
884 (match-end 3))
885 nil)
886 (if (match-beginning 4)
887 (substring entry-main
888 (match-beginning 4)
889 (match-end 4))
890 nil)))
891 (endtimestring (icalendar--diarytime-to-isotime
892 (if (match-beginning 6)
893 (substring entry-main
894 (match-beginning 6)
895 (match-end 6))
896 nil)
897 (if (match-beginning 7)
898 (substring entry-main
899 (match-beginning 7)
900 (match-end 7))
901 nil)))
902 (summary (icalendar--convert-string-for-export
903 (substring entry-main (match-beginning 8)
904 (match-end 8)))))
905 (icalendar--dmsg "weekly %s" entry-main)
906
907 (when starttimestring
908 (unless endtimestring
909 (let ((time (read
910 (icalendar--rris "^T0?" ""
911 starttimestring))))
912 (setq endtimestring (format "T%06d"
913 (+ 10000 time))))))
914 (concat "\nDTSTART;"
915 (if starttimestring
916 "VALUE=DATE-TIME:"
917 "VALUE=DATE:")
918 ;; find the correct week day,
919 ;; 1st january 2000 was a saturday
920 (format
921 "200001%02d"
922 (+ (icalendar--get-weekday-number day) 2))
923 (or starttimestring "")
924 "\nDTEND;"
925 (if endtimestring
926 "VALUE=DATE-TIME:"
927 "VALUE=DATE:")
928 (format
929 "200001%02d"
930 ;; end is non-inclusive!
931 (+ (icalendar--get-weekday-number day)
932 (if endtimestring 2 3)))
933 (or endtimestring "")
934 "\nSUMMARY:" summary
935 "\nRRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY="
936 day))
937 ;; no match
938 nil))
939
940 (defun icalendar--convert-yearly-to-ical (nonmarker entry-main)
941 "Convert yearly diary entry to icalendar format.
942
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-+")
949 "\\*?\\s-*"
950 "\\(0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
951 "\\("
952 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
953 "\\)?"
954 "\\s-*\\([^0-9]+.*\\)$" ; must not match years
955 )
956 entry-main)
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
969 (match-beginning 4)
970 (match-end 4))
971 nil)
972 (if (match-beginning 5)
973 (substring entry-main
974 (match-beginning 5)
975 (match-end 5))
976 nil)))
977 (endtimestring (icalendar--diarytime-to-isotime
978 (if (match-beginning 7)
979 (substring entry-main
980 (match-beginning 7)
981 (match-end 7))
982 nil)
983 (if (match-beginning 8)
984 (substring entry-main
985 (match-beginning 8)
986 (match-end 8))
987 nil)))
988 (summary (icalendar--convert-string-for-export
989 (substring entry-main (match-beginning 9)
990 (match-end 9)))))
991 (icalendar--dmsg "yearly %s" entry-main)
992
993 (when starttimestring
994 (unless endtimestring
995 (let ((time (read
996 (icalendar--rris "^T0?" ""
997 starttimestring))))
998 (setq endtimestring (format "T%06d"
999 (+ 10000 time))))))
1000 (concat "\nDTSTART;"
1001 (if starttimestring "VALUE=DATE-TIME:"
1002 "VALUE=DATE:")
1003 (format "1900%02d%02d" month day)
1004 (or starttimestring "")
1005 "\nDTEND;"
1006 (if endtimestring "VALUE=DATE-TIME:"
1007 "VALUE=DATE:")
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 "")
1013 "\nSUMMARY:"
1014 summary
1015 "\nRRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH="
1016 (format "%2d" month)
1017 ";BYMONTHDAY="
1018 (format "%2d" day)))
1019 ;; no match
1020 nil))
1021
1022 (defun icalendar--convert-sexp-to-ical (nonmarker entry-main)
1023 "Convert complex sexp diary entry to icalendar format -- unsupported!
1024
1025 FIXME!
1026
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-*\\(.*\\)")
1031 entry-main)
1032 (progn
1033 (icalendar--dmsg "diary-sexp %s" entry-main)
1034 (error "Sexp-entries are not supported yet"))
1035 ;; no match
1036 nil))
1037
1038 (defun icalendar--convert-block-to-ical (nonmarker entry-main)
1039 "Convert block diary entry to icalendar format.
1040
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\\)?"
1047 "\\("
1048 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1049 "\\)?"
1050 "\\s-*\\(.*\\)")
1051 entry-main)
1052 (let* ((startstring (substring entry-main
1053 (match-beginning 1)
1054 (match-end 1)))
1055 (endstring (substring entry-main
1056 (match-beginning 2)
1057 (match-end 2)))
1058 (startisostring (icalendar--datestring-to-isodate
1059 startstring))
1060 (endisostring (icalendar--datestring-to-isodate
1061 endstring))
1062 (endisostring+1 (icalendar--datestring-to-isodate
1063 endstring 1))
1064 (starttimestring (icalendar--diarytime-to-isotime
1065 (if (match-beginning 4)
1066 (substring entry-main
1067 (match-beginning 4)
1068 (match-end 4))
1069 nil)
1070 (if (match-beginning 5)
1071 (substring entry-main
1072 (match-beginning 5)
1073 (match-end 5))
1074 nil)))
1075 (endtimestring (icalendar--diarytime-to-isotime
1076 (if (match-beginning 7)
1077 (substring entry-main
1078 (match-beginning 7)
1079 (match-end 7))
1080 nil)
1081 (if (match-beginning 8)
1082 (substring entry-main
1083 (match-beginning 8)
1084 (match-end 8))
1085 nil)))
1086 (summary (icalendar--convert-string-for-export
1087 (substring entry-main (match-beginning 9)
1088 (match-end 9)))))
1089 (icalendar--dmsg "diary-block %s" entry-main)
1090 (when starttimestring
1091 (unless endtimestring
1092 (let ((time
1093 (read (icalendar--rris "^T0?" ""
1094 starttimestring))))
1095 (setq endtimestring (format "T%06d"
1096 (+ 10000 time))))))
1097 (if starttimestring
1098 ;; with time -> write rrule
1099 (concat "\nDTSTART;VALUE=DATE-TIME:"
1100 startisostring
1101 starttimestring
1102 "\nDTEND;VALUE=DATE-TIME:"
1103 startisostring
1104 endtimestring
1105 "\nSUMMARY:"
1106 summary
1107 "\nRRULE:FREQ=DAILY;INTERVAL=1;UNTIL="
1108 endisostring)
1109 ;; no time -> write long event
1110 (concat "\nDTSTART;VALUE=DATE:" startisostring
1111 "\nDTEND;VALUE=DATE:" endisostring+1
1112 "\nSUMMARY:" summary)))
1113 ;; no match
1114 nil))
1115
1116 (defun icalendar--convert-float-to-ical (nonmarker entry-main)
1117 "Convert float diary entry to icalendar format -- unsupported!
1118
1119 FIXME!
1120
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-*\\(.*\\)")
1125 entry-main)
1126 (progn
1127 (icalendar--dmsg "diary-float %s" entry-main)
1128 (error "`diary-float' is not supported yet"))
1129 ;; no match
1130 nil))
1131
1132 (defun icalendar--convert-date-to-ical (nonmarker entry-main)
1133 "Convert `diary-date' diary entry to icalendar format -- unsupported!
1134
1135 FIXME!
1136
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-*\\(.*\\)")
1141 entry-main)
1142 (progn
1143 (icalendar--dmsg "diary-date %s" entry-main)
1144 (error "`diary-date' is not supported yet"))
1145 ;; no match
1146 nil))
1147
1148 (defun icalendar--convert-cyclic-to-ical (nonmarker entry-main)
1149 "Convert `diary-cyclic' diary entry to icalendar format.
1150
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\\)?"
1157 "\\("
1158 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1159 "\\)?"
1160 "\\s-*\\(.*\\)")
1161 entry-main)
1162 (let* ((frequency (substring entry-main (match-beginning 1)
1163 (match-end 1)))
1164 (datetime (substring entry-main (match-beginning 2)
1165 (match-end 2)))
1166 (startisostring (icalendar--datestring-to-isodate
1167 datetime))
1168 (endisostring (icalendar--datestring-to-isodate
1169 datetime))
1170 (endisostring+1 (icalendar--datestring-to-isodate
1171 datetime 1))
1172 (starttimestring (icalendar--diarytime-to-isotime
1173 (if (match-beginning 4)
1174 (substring entry-main
1175 (match-beginning 4)
1176 (match-end 4))
1177 nil)
1178 (if (match-beginning 5)
1179 (substring entry-main
1180 (match-beginning 5)
1181 (match-end 5))
1182 nil)))
1183 (endtimestring (icalendar--diarytime-to-isotime
1184 (if (match-beginning 7)
1185 (substring entry-main
1186 (match-beginning 7)
1187 (match-end 7))
1188 nil)
1189 (if (match-beginning 8)
1190 (substring entry-main
1191 (match-beginning 8)
1192 (match-end 8))
1193 nil)))
1194 (summary (icalendar--convert-string-for-export
1195 (substring entry-main (match-beginning 9)
1196 (match-end 9)))))
1197 (icalendar--dmsg "diary-cyclic %s" entry-main)
1198 (when starttimestring
1199 (unless endtimestring
1200 (let ((time
1201 (read (icalendar--rris "^T0?" ""
1202 starttimestring))))
1203 (setq endtimestring (format "T%06d"
1204 (+ 10000 time))))))
1205 (concat "\nDTSTART;"
1206 (if starttimestring "VALUE=DATE-TIME:"
1207 "VALUE=DATE:")
1208 startisostring
1209 (or starttimestring "")
1210 "\nDTEND;"
1211 (if endtimestring "VALUE=DATE-TIME:"
1212 "VALUE=DATE:")
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...
1219 ))
1220 ;; no match
1221 nil))
1222
1223 (defun icalendar--convert-anniversary-to-ical (nonmarker entry-main)
1224 "Convert `diary-anniversary' diary entry to icalendar format.
1225
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\\)?"
1231 "\\("
1232 "-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
1233 "\\)?"
1234 "\\s-*\\(.*\\)")
1235 entry-main)
1236 (let* ((datetime (substring entry-main (match-beginning 1)
1237 (match-end 1)))
1238 (startisostring (icalendar--datestring-to-isodate
1239 datetime))
1240 (endisostring (icalendar--datestring-to-isodate
1241 datetime 1))
1242 (starttimestring (icalendar--diarytime-to-isotime
1243 (if (match-beginning 3)
1244 (substring entry-main
1245 (match-beginning 3)
1246 (match-end 3))
1247 nil)
1248 (if (match-beginning 4)
1249 (substring entry-main
1250 (match-beginning 4)
1251 (match-end 4))
1252 nil)))
1253 (endtimestring (icalendar--diarytime-to-isotime
1254 (if (match-beginning 6)
1255 (substring entry-main
1256 (match-beginning 6)
1257 (match-end 6))
1258 nil)
1259 (if (match-beginning 7)
1260 (substring entry-main
1261 (match-beginning 7)
1262 (match-end 7))
1263 nil)))
1264 (summary (icalendar--convert-string-for-export
1265 (substring entry-main (match-beginning 8)
1266 (match-end 8)))))
1267 (icalendar--dmsg "diary-anniversary %s" entry-main)
1268 (when starttimestring
1269 (unless endtimestring
1270 (let ((time
1271 (read (icalendar--rris "^T0?" ""
1272 starttimestring))))
1273 (setq endtimestring (format "T%06d"
1274 (+ 10000 time))))))
1275 (concat "\nDTSTART;"
1276 (if starttimestring "VALUE=DATE-TIME:"
1277 "VALUE=DATE:")
1278 startisostring
1279 (or starttimestring "")
1280 "\nDTEND;"
1281 (if endtimestring "VALUE=DATE-TIME:"
1282 "VALUE=DATE:")
1283 endisostring
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?!
1291 ";BYMONTH="
1292 (substring startisostring 4 6)
1293 ";BYMONTHDAY="
1294 (substring startisostring 6 8)))
1295 ;; no match
1296 nil))
1112 1297
1113 ;; ====================================================================== 1298 ;; ======================================================================
1114 ;; Import -- convert icalendar to emacs-diary 1299 ;; Import -- convert icalendar to emacs-diary
1115 ;; ====================================================================== 1300 ;; ======================================================================
1116 1301
1168 (message "Converting icalendar...") 1353 (message "Converting icalendar...")
1169 (setq ical-errors (icalendar--convert-ical-to-diary 1354 (setq ical-errors (icalendar--convert-ical-to-diary
1170 ical-contents 1355 ical-contents
1171 diary-file do-not-ask non-marking)) 1356 diary-file do-not-ask non-marking))
1172 (when diary-file 1357 (when diary-file
1173 ;; save the diary file 1358 ;; save the diary file if it is visited already
1174 (save-current-buffer 1359 (let ((b (find-buffer-visiting diary-file)))
1175 (set-buffer (find-buffer-visiting diary-file)) 1360 (when b
1176 (save-buffer))) 1361 (save-current-buffer
1362 (set-buffer b)
1363 (save-buffer)))))
1177 (message "Converting icalendar...done") 1364 (message "Converting icalendar...done")
1178 ;; return t if no error occured 1365 ;; return t if no error occured
1179 (not ical-errors)) 1366 (not ical-errors))
1180 (message 1367 (message
1181 "Current buffer does not contain icalendar contents!") 1368 "Current buffer does not contain icalendar contents!")
1182 ;; return nil, i.e. import did not work 1369 ;; return nil, i.e. import did not work
1183 nil))) 1370 nil)))
1184 1371
1185 (defalias 'icalendar-extract-ical-from-buffer 'icalendar-import-buffer) 1372 (defalias 'icalendar-extract-ical-from-buffer 'icalendar-import-buffer)
1186 (make-obsolete 'icalendar-extract-ical-from-buffer 'icalendar-import-buffer) 1373 (make-obsolete 'icalendar-extract-ical-from-buffer 'icalendar-import-buffer)
1187
1188 ;; ======================================================================
1189 ;; private area
1190 ;; ======================================================================
1191 1374
1192 (defun icalendar--format-ical-event (event) 1375 (defun icalendar--format-ical-event (event)
1193 "Create a string representation of an iCalendar EVENT." 1376 "Create a string representation of an iCalendar EVENT."
1194 (let ((string icalendar-import-format) 1377 (let ((string icalendar-import-format)
1195 (conversion-list 1378 (conversion-list
1224 DIARY-FILE. If DO-NOT-ASK is nil the user is asked for each event 1407 DIARY-FILE. If DO-NOT-ASK is nil the user is asked for each event
1225 whether to actually import it. NON-MARKING determines whether diary 1408 whether to actually import it. NON-MARKING determines whether diary
1226 events are created as non-marking. 1409 events are created as non-marking.
1227 This function attempts to return t if something goes wrong. In this 1410 This function attempts to return t if something goes wrong. In this
1228 case an error string which describes all the errors and problems is 1411 case an error string which describes all the errors and problems is
1229 written into the buffer ` *icalendar-errors*'." 1412 written into the buffer `*icalendar-errors*'."
1230 (let* ((ev (icalendar--all-events ical-list)) 1413 (let* ((ev (icalendar--all-events ical-list))
1231 (error-string "") 1414 (error-string "")
1232 (event-ok t) 1415 (event-ok t)
1233 (found-error nil) 1416 (found-error nil)
1234 e diary-string) 1417 e diary-string)
1236 (while ev 1419 (while ev
1237 (setq e (car ev)) 1420 (setq e (car ev))
1238 (setq ev (cdr ev)) 1421 (setq ev (cdr ev))
1239 (setq event-ok nil) 1422 (setq event-ok nil)
1240 (condition-case error-val 1423 (condition-case error-val
1241 (let* ((dtstart (icalendar--decode-isodatetime 1424 (let* ((dtstart (icalendar--get-event-property e 'DTSTART))
1242 (icalendar--get-event-property e 'DTSTART))) 1425 (dtstart-dec (icalendar--decode-isodatetime dtstart))
1243 (start-d (icalendar--datetime-to-diary-date 1426 (start-d (icalendar--datetime-to-diary-date
1244 dtstart)) 1427 dtstart-dec))
1245 (start-t (icalendar--datetime-to-colontime dtstart)) 1428 (start-t (icalendar--datetime-to-colontime dtstart-dec))
1246 (dtend (icalendar--decode-isodatetime 1429 (dtend (icalendar--get-event-property e 'DTEND))
1247 (icalendar--get-event-property e 'DTEND))) 1430 (dtend-dec (icalendar--decode-isodatetime dtend))
1431 (dtend-1-dec (icalendar--decode-isodatetime dtend -1))
1248 end-d 1432 end-d
1433 end-1-d
1249 end-t 1434 end-t
1250 (subject (icalendar--convert-string-for-import 1435 (subject (icalendar--convert-string-for-import
1251 (or (icalendar--get-event-property e 'SUMMARY) 1436 (or (icalendar--get-event-property e 'SUMMARY)
1252 "No Subject"))) 1437 "No Subject")))
1253 (rrule (icalendar--get-event-property e 'RRULE)) 1438 (rrule (icalendar--get-event-property e 'RRULE))
1254 (rdate (icalendar--get-event-property e 'RDATE)) 1439 (rdate (icalendar--get-event-property e 'RDATE))
1255 (duration (icalendar--get-event-property e 'DURATION))) 1440 (duration (icalendar--get-event-property e 'DURATION)))
1256 (icalendar--dmsg "%s: %s" start-d subject) 1441 (icalendar--dmsg "%s: `%s'" start-d subject)
1257 ;; check whether start-time is missing 1442 ;; check whether start-time is missing
1258 (if (and (icalendar--get-event-property-attributes 1443 (if (and dtstart
1259 e 'DTSTART) 1444 (string=
1260 (string= (cadr (icalendar--get-event-property-attributes 1445 (cadr (icalendar--get-event-property-attributes
1261 e 'DTSTART)) 1446 e 'DTSTART))
1262 "DATE")) 1447 "DATE"))
1263 (setq start-t nil)) 1448 (setq start-t nil))
1264 (when duration 1449 (when duration
1265 (let ((dtend2 (icalendar--add-decoded-times 1450 (let ((dtend-dec-d (icalendar--add-decoded-times
1266 dtstart 1451 dtstart-dec
1267 (icalendar--decode-isoduration duration)))) 1452 (icalendar--decode-isoduration duration)))
1268 (if (and dtend (not (eq dtend dtend2))) 1453 (dtend-1-dec-d (icalendar--add-decoded-times
1454 dtstart-dec
1455 (icalendar--decode-isoduration duration
1456 t))))
1457 (if (and dtend-dec (not (eq dtend-dec dtend-dec-d)))
1269 (message "Inconsistent endtime and duration for %s" 1458 (message "Inconsistent endtime and duration for %s"
1270 subject)) 1459 subject))
1271 (setq dtend dtend2))) 1460 (setq dtend-dec dtend-dec-d)
1272 (setq end-d (if dtend 1461 (setq dtend-1-dec dtend-1-dec-d)))
1273 (icalendar--datetime-to-diary-date dtend) 1462 (setq end-d (if dtend-dec
1463 (icalendar--datetime-to-diary-date dtend-dec)
1274 start-d)) 1464 start-d))
1275 (setq end-t (if dtend 1465 (setq end-1-d (if dtend-1-dec
1276 (icalendar--datetime-to-colontime dtend) 1466 (icalendar--datetime-to-diary-date dtend-1-dec)
1467 start-d))
1468 (setq end-t (if (and
1469 dtend-dec
1470 (not (string=
1471 (cadr
1472 (icalendar--get-event-property-attributes
1473 e 'DTEND))
1474 "DATE")))
1475 (icalendar--datetime-to-colontime dtend-dec)
1277 start-t)) 1476 start-t))
1278 (icalendar--dmsg "start-d: %s, end-d: %s" start-d end-d) 1477 (icalendar--dmsg "start-d: %s, end-d: %s" start-d end-d)
1279 (cond 1478 (cond
1280 ;; recurring event 1479 ;; recurring event
1281 (rrule 1480 (rrule
1282 (icalendar--dmsg "recurring event") 1481 (setq diary-string
1283 (let* ((rrule-props (icalendar--split-value rrule)) 1482 (icalendar--convert-recurring-to-diary e dtstart-dec start-t
1284 (frequency (cadr (assoc 'FREQ rrule-props))) 1483 end-t))
1285 (until (cadr (assoc 'UNTIL rrule-props))) 1484 (setq event-ok t))
1286 (interval (read (cadr (assoc 'INTERVAL rrule-props)))))
1287 (cond ((string-equal frequency "WEEKLY")
1288 (if (not start-t)
1289 (progn
1290 ;; weekly and all-day
1291 (icalendar--dmsg "weekly all-day")
1292 (if until
1293 (let ((fro
1294 (icalendar--datetime-to-diary-date
1295 (icalendar--decode-isodatetime
1296 (icalendar--get-event-property
1297 e
1298 'DTSTART))))
1299 (unt
1300 (icalendar--datetime-to-diary-date
1301 (icalendar--decode-isodatetime
1302 until -1))))
1303 (setq diary-string
1304 (format
1305 (concat "%%%%(and "
1306 "(diary-cyclic %d %s) "
1307 "(diary-block %s %s))")
1308 (* interval 7)
1309 (icalendar--datetime-to-diary-date
1310 dtstart)
1311 (icalendar--datetime-to-diary-date
1312 dtstart)
1313 (icalendar--datetime-to-diary-date
1314 (icalendar--decode-isodatetime
1315 until -1)))))
1316 (setq diary-string
1317 (format "%%%%(and (diary-cyclic %d %s))"
1318 (* interval 7)
1319 (icalendar--datetime-to-diary-date
1320 dtstart))))
1321 (setq event-ok t))
1322 ;; weekly and not all-day
1323 (let* ((byday (cadr (assoc 'BYDAY rrule-props)))
1324 (weekday
1325 (icalendar--get-weekday-number byday)))
1326 (icalendar--dmsg "weekly not-all-day")
1327 (if until
1328 (let ((fro
1329 (icalendar--datetime-to-diary-date
1330 (icalendar--decode-isodatetime
1331 (icalendar--get-event-property
1332 e
1333 'DTSTART))))
1334 (unt
1335 (icalendar--datetime-to-diary-date
1336 (icalendar--decode-isodatetime
1337 until))))
1338 (setq diary-string
1339 (format
1340 (concat "%%%%(and "
1341 "(diary-cyclic %d %s) "
1342 "(diary-block %s %s)) "
1343 "%s%s%s")
1344 (* interval 7)
1345 (icalendar--datetime-to-diary-date
1346 dtstart)
1347 (icalendar--datetime-to-diary-date
1348 dtstart)
1349 (icalendar--datetime-to-diary-date
1350 (icalendar--decode-isodatetime
1351 until))
1352 start-t
1353 (if end-t "-" "") (or end-t ""))))
1354 ;; no limit
1355 ;; FIXME!!!!
1356 ;; DTSTART;VALUE=DATE-TIME:20030919T090000
1357 ;; DTEND;VALUE=DATE-TIME:20030919T113000
1358 (setq diary-string
1359 (format
1360 "%%%%(and (diary-cyclic %s %s)) %s%s%s"
1361 (* interval 7)
1362 (icalendar--datetime-to-diary-date
1363 dtstart)
1364 start-t
1365 (if end-t "-" "") (or end-t ""))))
1366 (setq event-ok t))))
1367 ;; yearly
1368 ((string-equal frequency "YEARLY")
1369 (icalendar--dmsg "yearly")
1370 (setq diary-string
1371 (format
1372 "%%%%(and (diary-anniversary %s))"
1373 (icalendar--datetime-to-diary-date dtstart)))
1374 (setq event-ok t))
1375 ;; FIXME: war auskommentiert:
1376 ((and (string-equal frequency "DAILY")
1377 ;;(not (string= start-d end-d))
1378 ;;(not start-t)
1379 ;;(not end-t)
1380 )
1381 (let ((ds (icalendar--datetime-to-diary-date
1382 (icalendar--decode-isodatetime
1383 (icalendar--get-event-property
1384 e 'DTSTART))))
1385 (de (icalendar--datetime-to-diary-date
1386 (icalendar--decode-isodatetime
1387 until -1))))
1388 (setq diary-string
1389 (format
1390 "%%%%(and (diary-block %s %s))"
1391 ds de)))
1392 (setq event-ok t))))
1393 ;; Handle exceptions from recurrence rules
1394 (let ((ex-dates (icalendar--get-event-properties e
1395 'EXDATE)))
1396 (while ex-dates
1397 (let* ((ex-start (icalendar--decode-isodatetime
1398 (car ex-dates)))
1399 (ex-d (icalendar--datetime-to-diary-date
1400 ex-start)))
1401 (setq diary-string
1402 (icalendar--rris "^%%(\\(and \\)?"
1403 (format
1404 "%%%%(and (not (diary-date %s)) "
1405 ex-d)
1406 diary-string)))
1407 (setq ex-dates (cdr ex-dates))))
1408 ;; FIXME: exception rules are not recognized
1409 (if (icalendar--get-event-property e 'EXRULE)
1410 (setq diary-string
1411 (concat diary-string
1412 "\n Exception rules: "
1413 (icalendar--get-event-properties
1414 e 'EXRULE)))))
1415 (rdate 1485 (rdate
1416 (icalendar--dmsg "rdate event") 1486 (icalendar--dmsg "rdate event")
1417 (setq diary-string "") 1487 (setq diary-string "")
1418 (mapcar (lambda (datestring) 1488 (mapcar (lambda (datestring)
1419 (setq diary-string 1489 (setq diary-string
1421 (format "......")))) 1491 (format "......"))))
1422 (icalendar--split-value rdate))) 1492 (icalendar--split-value rdate)))
1423 ;; non-recurring event 1493 ;; non-recurring event
1424 ;; all-day event 1494 ;; all-day event
1425 ((not (string= start-d end-d)) 1495 ((not (string= start-d end-d))
1426 (icalendar--dmsg "non-recurring event") 1496 (setq diary-string
1427 (let ((ds (icalendar--datetime-to-diary-date dtstart)) 1497 (icalendar--convert-non-recurring-all-day-to-diary
1428 (de (icalendar--datetime-to-diary-date dtend))) 1498 e start-d end-1-d))
1429 (setq diary-string
1430 (format "%%%%(and (diary-block %s %s))"
1431 ds de)))
1432 (setq event-ok t)) 1499 (setq event-ok t))
1433 ;; not all-day 1500 ;; not all-day
1434 ((and start-t (or (not end-t) 1501 ((and start-t (or (not end-t)
1435 (not (string= start-t end-t)))) 1502 (not (string= start-t end-t))))
1436 (icalendar--dmsg "not all day event") 1503 (setq diary-string
1437 (cond (end-t 1504 (icalendar--convert-non-recurring-not-all-day-to-diary
1438 (setq diary-string 1505 e dtstart-dec dtend-dec start-t end-t))
1439 (format "%s %s-%s"
1440 (icalendar--datetime-to-diary-date
1441 dtstart "/")
1442 start-t end-t)))
1443 (t
1444 (setq diary-string
1445 (format "%s %s"
1446 (icalendar--datetime-to-diary-date
1447 dtstart "/")
1448 start-t))))
1449 (setq event-ok t)) 1506 (setq event-ok t))
1450 ;; all-day event 1507 ;; all-day event
1451 (t 1508 (t
1452 (icalendar--dmsg "all day event") 1509 (icalendar--dmsg "all day event")
1453 (setq diary-string (icalendar--datetime-to-diary-date 1510 (setq diary-string (icalendar--datetime-to-diary-date
1454 dtstart "/")) 1511 dtstart-dec "/"))
1455 (setq event-ok t))) 1512 (setq event-ok t)))
1456 ;; add all other elements unless the user doesn't want to have 1513 ;; add all other elements unless the user doesn't want to have
1457 ;; them 1514 ;; them
1458 (if event-ok 1515 (if event-ok
1459 (progn 1516 (progn
1476 (setq error-string (format "%s\n%s\nCannot handle this event: %s" 1533 (setq error-string (format "%s\n%s\nCannot handle this event: %s"
1477 error-val error-string e)) 1534 error-val error-string e))
1478 (message error-string)))) 1535 (message error-string))))
1479 (if found-error 1536 (if found-error
1480 (save-current-buffer 1537 (save-current-buffer
1481 (set-buffer (get-buffer-create " *icalendar-errors*")) 1538 (set-buffer (get-buffer-create "*icalendar-errors*"))
1482 (erase-buffer) 1539 (erase-buffer)
1483 (insert error-string))) 1540 (insert error-string)))
1484 (message "Converting icalendar...done") 1541 (message "Converting icalendar...done")
1485 found-error)) 1542 found-error))
1543
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.
1547
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)))
1563 (result ""))
1564
1565 ;; FIXME FIXME interval!!!!!!!!!!!!!
1566
1567 (when count
1568 (if until
1569 (message "Must not have UNTIL and COUNT -- ignoring COUNT element!")
1570 (let ((until-1 0))
1571 (cond ((string-equal frequency "DAILY")
1572 (setq until (icalendar--add-decoded-times
1573 dtstart-dec
1574 (list 0 0 0 (* (read count) interval) 0 0)))
1575 (setq until-1 (icalendar--add-decoded-times
1576 dtstart-dec
1577 (list 0 0 0 (* (- (read count) 1) interval)
1578 0 0)))
1579 )
1580 ((string-equal frequency "WEEKLY")
1581 (setq until (icalendar--add-decoded-times
1582 dtstart-dec
1583 (list 0 0 0 (* (read count) 7 interval) 0 0)))
1584 (setq until-1 (icalendar--add-decoded-times
1585 dtstart-dec
1586 (list 0 0 0 (* (- (read count) 1) 7
1587 interval) 0 0)))
1588 )
1589 ((string-equal frequency "MONTHLY")
1590 (setq until (icalendar--add-decoded-times
1591 dtstart-dec (list 0 0 0 0 (* (- (read count) 1)
1592 interval) 0)))
1593 (setq until-1 (icalendar--add-decoded-times
1594 dtstart-dec (list 0 0 0 0 (* (- (read count) 1)
1595 interval) 0)))
1596 )
1597 ((string-equal frequency "YEARLY")
1598 (setq until (icalendar--add-decoded-times
1599 dtstart-dec (list 0 0 0 0 0 (* (- (read count) 1)
1600 interval))))
1601 (setq until-1 (icalendar--add-decoded-times
1602 dtstart-dec
1603 (list 0 0 0 0 0 (* (- (read count) 1)
1604 interval))))
1605 )
1606 (t
1607 (message "Cannot handle COUNT attribute for `%s' events."
1608 frequency)))
1609 (setq until-conv (icalendar--datetime-to-diary-date until))
1610 (setq until-1-conv (icalendar--datetime-to-diary-date until-1))
1611 ))
1612 )
1613 (cond ((string-equal frequency "WEEKLY")
1614 (if (not start-t)
1615 (progn
1616 ;; weekly and all-day
1617 (icalendar--dmsg "weekly all-day")
1618 (if until
1619 (setq result
1620 (format
1621 (concat "%%%%(and "
1622 "(diary-cyclic %d %s) "
1623 "(diary-block %s %s))")
1624 (* interval 7)
1625 dtstart-conv
1626 dtstart-conv
1627 (if count until-1-conv until-conv)
1628 ))
1629 (setq result
1630 (format "%%%%(and (diary-cyclic %d %s))"
1631 (* interval 7)
1632 dtstart-conv))))
1633 ;; weekly and not all-day
1634 (let* ((byday (cadr (assoc 'BYDAY rrule-props)))
1635 (weekday
1636 (icalendar--get-weekday-number byday)))
1637 (icalendar--dmsg "weekly not-all-day")
1638 (if until
1639 (setq result
1640 (format
1641 (concat "%%%%(and "
1642 "(diary-cyclic %d %s) "
1643 "(diary-block %s %s)) "
1644 "%s%s%s")
1645 (* interval 7)
1646 dtstart-conv
1647 dtstart-conv
1648 until-conv
1649 (or start-t "")
1650 (if end-t "-" "") (or end-t "")))
1651 ;; no limit
1652 ;; FIXME!!!!
1653 ;; DTSTART;VALUE=DATE-TIME:20030919T090000
1654 ;; DTEND;VALUE=DATE-TIME:20030919T113000
1655 (setq result
1656 (format
1657 "%%%%(and (diary-cyclic %s %s)) %s%s%s"
1658 (* interval 7)
1659 dtstart-conv
1660 (or start-t "")
1661 (if end-t "-" "") (or end-t "")))))))
1662 ;; yearly
1663 ((string-equal frequency "YEARLY")
1664 (icalendar--dmsg "yearly")
1665 (if until
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))
1673 dtstart-conv
1674 until-conv
1675 (or start-t "")
1676 (if end-t "-" "") (or end-t "")))
1677 (setq result (format
1678 "%%%%(and (diary-anniversary %s)) %s%s%s"
1679 dtstart-conv
1680 (or start-t "")
1681 (if end-t "-" "") (or end-t "")))))
1682 ;; monthly
1683 ((string-equal frequency "MONTHLY")
1684 (icalendar--dmsg "monthly")
1685 (setq result
1686 (format
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))
1690 "t"
1691 dtstart-conv
1692 (if until
1693 until-conv
1694 "1 1 9999") ;; FIXME: should be unlimited
1695 (or start-t "")
1696 (if end-t "-" "") (or end-t ""))))
1697 ;; daily
1698 ((and (string-equal frequency "DAILY"))
1699 (if until
1700 (setq result
1701 (format
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)
1706 (or start-t "")
1707 (if end-t "-" "") (or end-t "")))
1708 (setq result
1709 (format
1710 "%%%%(and (diary-cyclic %s %s)) %s%s%s"
1711 interval
1712 dtstart-conv
1713 (or start-t "")
1714 (if end-t "-" "") (or end-t ""))))))
1715 ;; Handle exceptions from recurrence rules
1716 (let ((ex-dates (icalendar--get-event-properties e 'EXDATE)))
1717 (while ex-dates
1718 (let* ((ex-start (icalendar--decode-isodatetime
1719 (car ex-dates)))
1720 (ex-d (icalendar--datetime-to-diary-date
1721 ex-start)))
1722 (setq result
1723 (icalendar--rris "^%%(\\(and \\)?"
1724 (format
1725 "%%%%(and (not (diary-date %s)) "
1726 ex-d)
1727 result)))
1728 (setq ex-dates (cdr ex-dates))))
1729 ;; FIXME: exception rules are not recognized
1730 (if (icalendar--get-event-property e 'EXRULE)
1731 (setq result
1732 (concat result
1733 "\n Exception rules: "
1734 (icalendar--get-event-properties
1735 e 'EXRULE))))
1736 result))
1737
1738 (defun icalendar--convert-non-recurring-all-day-to-diary (event start-d end-d)
1739 "Convert non-recurring icalendar EVENT to diary format.
1740
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))
1746
1747 (defun icalendar--convert-non-recurring-not-all-day-to-diary (event dtstart-dec
1748 dtend-dec
1749 start-t
1750 end-t)
1751 "Convert recurring icalendar EVENT to diary format.
1752
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")
1758 (cond (end-t
1759 (format "%s %s-%s"
1760 (icalendar--datetime-to-diary-date
1761 dtstart-dec "/")
1762 start-t end-t))
1763 (t
1764 (format "%s %s"
1765 (icalendar--datetime-to-diary-date
1766 dtstart-dec "/")
1767 start-t))))
1486 1768
1487 (defun icalendar--add-diary-entry (string diary-file non-marking 1769 (defun icalendar--add-diary-entry (string diary-file non-marking
1488 &optional subject) 1770 &optional subject)
1489 "Add STRING to the diary file DIARY-FILE. 1771 "Add STRING to the diary file DIARY-FILE.
1490 STRING must be a properly formatted valid diary entry. NON-MARKING 1772 STRING must be a properly formatted valid diary entry. NON-MARKING