(defcustom icalendar-uid-format
"emacs%t%c"
- "Format of unique ID code (UID) for each iCalendar object.
-The following specifiers are available:
+ "Format of unique ID code (UID) for each iCalendar object.
+The following specifiers are available:
%c COUNTER, an integer value that is increased each time a uid is
- generated. This may be necessary for systems which do not
+ generated. This may be necessary for systems which do not
provide time-resolution finer than a second.
%h HASH, a hash value of the diary entry,
%s DTSTART, the start date (excluding time) of the diary entry,
%t TIMESTAMP, a unique creation timestamp,
-%u USERNAME, the user-login-name.
+%u USERNAME, the variable `user-login-name'.
For example, a value of \"%s_%h@mydomain.com\" will generate a
UID code for each entry composed of the time of the event, a hash
(goto-char (point-min))
(while
(re-search-forward
- "\\([A-Za-z0-9-]+\\)=\\(\\([^;,:]+\\)\\|\"\\([^\"]+\\)\"\\);?"
+ "\\([A-Za-z0-9-]+\\)=\\(\\([^;:]+\\)\\|\"\\([^\"]+\\)\"\\);?"
nil t)
(setq param-name (intern (match-string 1)))
(setq param-value (match-string 2))
;; Error:
-1))
+(defun icalendar--get-weekday-numbers (abbrevweekdays)
+ "Return the list of numbers for the comma-separated ABBREVWEEKDAYS."
+ (when abbrevweekdays
+ (let* ((num -1)
+ (weekday-alist (mapcar (lambda (day)
+ (progn
+ (setq num (1+ num))
+ (cons (downcase day) num)))
+ icalendar--weekday-array)))
+ (delq nil
+ (mapcar (lambda (abbrevday)
+ (cdr (assoc abbrevday weekday-alist)))
+ (split-string (downcase abbrevweekdays) ","))))))
+
(defun icalendar--get-weekday-abbrev (weekday)
"Return the abbreviated WEEKDAY."
(catch 'found
`icalendar--uid-count'. Returns the UID string."
(let ((uid icalendar-uid-format))
- (setq uid (replace-regexp-in-string
- "%c"
+ (setq uid (replace-regexp-in-string
+ "%c"
(format "%d" icalendar--uid-count)
uid t t))
(setq icalendar--uid-count (1+ icalendar--uid-count))
- (setq uid (replace-regexp-in-string
+ (setq uid (replace-regexp-in-string
"%t"
(format "%d%d%d" (car (current-time))
(cadr (current-time))
- (car (cddr (current-time))))
+ (car (cddr (current-time))))
uid t t))
- (setq uid (replace-regexp-in-string
- "%h"
+ (setq uid (replace-regexp-in-string
+ "%h"
(format "%d" (abs (sxhash entry-full))) uid t t))
- (setq uid (replace-regexp-in-string
+ (setq uid (replace-regexp-in-string
"%u" (or user-login-name "UNKNOWN_USER") uid t t))
(let ((dtstart (if (string-match "^DTSTART[^:]*:\\([0-9]*\\)" contents)
(substring contents (match-beginning 1) (match-end 1))
(if url
(setq contents (concat contents "\nURL:" url))))
- (setq header (concat "\nBEGIN:VEVENT\nUID:"
+ (setq header (concat "\nBEGIN:VEVENT\nUID:"
(icalendar--create-uid entry-full contents)))
(setq result (concat result header contents "\nEND:VEVENT")))
;; handle errors
(list "%u"
(concat "\\(" icalendar-import-format-url "\\)??"))))
;; Need the \' regexp in order to detect multi-line items
- (setq s (concat "\\`"
+ (setq s (concat "\\`"
(icalendar--rris "%s" "\\(.*?\\)" s nil t)
"\\'"))
(if (string-match s summary-and-rest)
))
)
(cond ((string-equal frequency "WEEKLY")
- (if (not start-t)
- (progn
- ;; weekly and all-day
- (icalendar--dmsg "weekly all-day")
- (if until
- (setq result
- (format
- (concat "%%%%(and "
- "(diary-cyclic %d %s) "
- "(diary-block %s %s))")
- (* interval 7)
- dtstart-conv
- dtstart-conv
- (if count until-1-conv until-conv)
- ))
- (setq result
- (format "%%%%(and (diary-cyclic %d %s))"
- (* interval 7)
- dtstart-conv))))
- ;; weekly and not all-day
- (let* ((byday (cadr (assoc 'BYDAY rrule-props)))
- (weekday
- (icalendar--get-weekday-number byday)))
+ (let* ((byday (cadr (assoc 'BYDAY rrule-props)))
+ (weekdays
+ (icalendar--get-weekday-numbers byday))
+ (weekday-clause
+ (when (> (length weekdays) 1)
+ (format "(memq (calendar-day-of-week date) '%s) "
+ weekdays))))
+ (if (not start-t)
+ (progn
+ ;; weekly and all-day
+ (icalendar--dmsg "weekly all-day")
+ (if until
+ (setq result
+ (format
+ (concat "%%%%(and "
+ "%s"
+ "(diary-block %s %s))")
+ (or weekday-clause
+ (format "(diary-cyclic %d %s) "
+ (* interval 7)
+ dtstart-conv))
+ (if count until-1-conv until-conv)
+ ))
+ (setq result
+ (format "%%%%(and %s(diary-cyclic %d %s))"
+ (or weekday-clause "")
+ (if weekday-clause 1 (* interval 7))
+ dtstart-conv))))
+ ;; weekly and not all-day
(icalendar--dmsg "weekly not-all-day")
(if until
(setq result
(format
(concat "%%%%(and "
- "(diary-cyclic %d %s) "
+ "%s"
"(diary-block %s %s)) "
"%s%s%s")
- (* interval 7)
- dtstart-conv
+ (or weekday-clause
+ (format "(diary-cyclic %d %s) "
+ (* interval 7)
+ dtstart-conv))
dtstart-conv
until-conv
(or start-t "")
;; DTEND;VALUE=DATE-TIME:20030919T113000
(setq result
(format
- "%%%%(and (diary-cyclic %s %s)) %s%s%s"
- (* interval 7)
- dtstart-conv
- (or start-t "")
+ "%%%%(and %s(diary-cyclic %d %s)) %s%s%s"
+ (or weekday-clause "")
+ (if weekday-clause 1 (* interval 7))
+ dtstart-conv
+ (or start-t "")
(if end-t "-" "") (or end-t "")))))))
;; yearly
((string-equal frequency "YEARLY")
(defun icalendar-testsuite-run ()
"Run icalendar test suite."
(interactive)
+ (icalendar-testsuite--run-internal-tests)
(icalendar-testsuite--run-function-tests)
(icalendar-testsuite--run-import-tests)
(icalendar-testsuite--run-export-tests)
(icalendar-testsuite--run-real-world-tests)
(message "All icalendar tests finished successfully."))
+;; ======================================================================
+;; internal
+;; ======================================================================
+(defun icalendar-testsuite--trim (string)
+ "Remove leading and trailing whitespace from STRING."
+ (replace-regexp-in-string "[ \t\n]+\\'" ""
+ (replace-regexp-in-string "\\`[ \t\n]+" "" string)))
+
+(defun icalendar-testsuite--compare-strings (str1 str2)
+ "Compare strings STR1 and STR2.
+Return t if strings are equal, else return substring indicating first difference.
+FIXME: make this a little smarter."
+ (let* ((s1 (icalendar-testsuite--trim str1))
+ (s2 (icalendar-testsuite--trim str2))
+ (result (compare-strings s1 0 nil s2 0 nil))
+ (len (length str2)))
+ (if (numberp result)
+ (if (> result 0)
+ (concat "..." (substring str2 (- result 1)
+ (min len (+ (- result 1) 3))) "...")
+ (concat "..." (substring str2 (- (+ result 1))
+ (min len (+ (- (+ result 1)) 3))) "..."))
+ t)))
+
+(defun icalendar-testsuite--run-internal-tests ()
+ "Run icalendar-testsuite internal tests."
+ (assert (equal t (icalendar-testsuite--compare-strings " abcde" "abcde ")))
+ (assert
+ (string= "...def..."
+ (icalendar-testsuite--compare-strings "abcxe" "abcdefghijklmn")))
+ (assert (string= "...xe..."
+ (icalendar-testsuite--compare-strings "abcde" "abcxe")))
+ (assert (string= "...ddd..."
+ (icalendar-testsuite--compare-strings "abc" "abcdddddd")))
+ (assert (string= "......"
+ (icalendar-testsuite--compare-strings "abcdefghij" "abc"))))
+
+
;; ======================================================================
;; Test methods for functions
;; ======================================================================
(assert (string= "subject" (cadr result)))
;; with time
- (setq result (icalendar--convert-ordinary-to-ical
+ (setq result (icalendar--convert-ordinary-to-ical
"&?" "&2010 2 15 12:34-23:45 s"))
(assert (= 2 (length result)))
(assert (string= (concat "\nDTSTART;VALUE=DATE-TIME:20100215T123400"
(assert (string= "s" (cadr result)))
;; with time, again -- test bug#5549
- (setq result (icalendar--convert-ordinary-to-ical
+ (setq result (icalendar--convert-ordinary-to-ical
"x?" "x2010 2 15 0:34-1:45 s"))
(assert (= 2 (length result)))
(assert (string= (concat "\nDTSTART;VALUE=DATE-TIME:20100215T003400"
"Test method for `icalendar--convert-block-to-ical'."
(let* ((calendar-date-style 'iso)
result)
- (setq result (icalendar--convert-block-to-ical
+ (setq result (icalendar--convert-block-to-ical
"" "%%(diary-block 2004 7 19 2004 8 27) Sommerferien"))
(assert (= 2 (length result)))
(assert (string= (concat
"Test method for `icalendar--convert-cyclic-to-ical'."
(let* ((calendar-date-style 'iso)
result)
- (setq result (icalendar--convert-block-to-ical
+ (setq result (icalendar--convert-block-to-ical
"" "%%(diary-block 2004 7 19 2004 8 27) Sommerferien"))
(assert (= 2 (length result)))
(assert (string= (concat
"Test method for `icalendar--convert-anniversary-to-ical'."
(let* ((calendar-date-style 'iso)
result)
- (setq result (icalendar--convert-anniversary-to-ical
+ (setq result (icalendar--convert-anniversary-to-ical
"" "%%(diary-anniversary 1964 6 30) g"))
(assert (= 2 (length result)))
(assert (string= (concat
))
(defun icalendar-testsuite--test-parse-vtimezone ()
+ "Test method for `icalendar--parse-vtimezone'."
(let (vtimezone result)
(setq vtimezone (icalendar-testsuite--get-ical-event "BEGIN:VTIMEZONE
TZID:thename
(setq result (icalendar--parse-vtimezone vtimezone))
(assert (string= "thename" (car result)))
(message (cdr result))
- (assert (string= "STD-02:00DST-03:00,M3.5.0/03:00:00,M10.5.0/04:00:00" (cdr result)))
+ (assert (string= "STD-02:00DST-03:00,M3.5.0/03:00:00,M10.5.0/04:00:00"
+ (cdr result)))
(setq vtimezone (icalendar-testsuite--get-ical-event "BEGIN:VTIMEZONE
TZID:anothername
BEGIN:STANDARD
(icalendar-testsuite--do-test-export input-iso expected-output)))
(when input-european
(let ((calendar-month-name-array
- ["Januar" "Februar" "März" "April" "Mai" "Juni" "Juli" "August"
+ ["Januar" "Februar" "März" "April" "Mai" "Juni" "Juli" "August"
"September" "Oktober" "November" "Dezember"])
(calendar-day-name-array
["Sonntag" "Montag" "Dienstag" "Mittwoch" "Donnerstag" "Freitag"
\\s-*$"
nil t)))
(error
- "Export test failed! Input: `%s'\nFound:\n\n%s\n\nbut expected\n\n%s"
+ "Export test failed! Input: `%s'\nFound:\n\n%s\n\nbut expected\n\n%s\n%s"
input
(or (and (match-beginning 1)
- (buffer-substring-no-properties (match-beginning 1) (match-end 1)))
+ (buffer-substring-no-properties (match-beginning 1)
+ (match-end 1)))
+ "<nil>")
+ (or expected-output "<nil>")
+ (icalendar-testsuite--compare-strings (or (and (match-beginning 1)
+ (buffer-substring-no-properties (match-beginning 1)
+ (match-end 1)))
"<nil>")
- (or expected-output "<nil>"))))
+ (or expected-output "<nil>")))))
(kill-buffer (find-buffer-visiting temp-file))
(delete-file temp-file)))
(icalendar-import-buffer temp-file t t)
(save-excursion
(find-file temp-file)
- (let ((result (buffer-substring-no-properties (point-min) (point-max))))
- (unless (string-match (concat "^\\s-*" expected-output "\\s-*$")
- result)
- (error "Import test failed! Found `%s'\nbut expected `%s'" result
- expected-output)))
+ (let* ((result (buffer-substring-no-properties (point-min) (point-max)))
+ (difference
+ (icalendar-testsuite--compare-strings result
+ expected-output)))
+ (if (stringp difference)
+ (error "Import test failed! Found\n`%s'\nbut expected\n`%s'\n%s'"
+ result expected-output difference)))
(kill-buffer (find-buffer-visiting temp-file))
(delete-file temp-file))))
(when (re-search-forward "\nUID:.*\n" nil t)
(replace-match "\n"))
(let ((cycled (buffer-substring-no-properties (point-min) (point-max))))
- (unless (string-equal org-input cycled)
- (error "Import test failed! Found `%s'\nbut expected `%s'" cycled
- org-input))))
+ (let ((difference (icalendar-testsuite--compare-strings cycled
+ org-input)))
+ (if (stringp difference)
+ (error "Import test failed! Found\n`%s'\nbut expected\n`%s'\n%s'"
+ cycled org-input difference)))
+ ))
;; clean up -- Note this is done only if test is passed
(kill-buffer (find-buffer-visiting temp-diary))
"&9/19/2003 non-recurring allday")
(icalendar-testsuite--test-import
+ ;; do not remove the trailing blank after "long"!
"SUMMARY:long
summary
DTSTART;VALUE=DATE:20030919"
DTEND;VALUE=DATE:20040828
DTSTAMP:20031103T011641Z
"
- "&%%(and (diary-block 2004 7 19 2004 8 27)) Sommerferien"
- "&%%(and (diary-block 19 7 2004 27 8 2004)) Sommerferien"
- "&%%(and (diary-block 7 19 2004 8 27 2004)) Sommerferien")
+ "&%%(and (diary-block 2004 7 19 2004 8 27)) Sommerferien
+ Status: TENTATIVE
+ Class: PRIVATE
+"
+ "&%%(and (diary-block 19 7 2004 27 8 2004)) Sommerferien
+ Status: TENTATIVE
+ Class: PRIVATE
+"
+ "&%%(and (diary-block 7 19 2004 8 27 2004)) Sommerferien
+ Status: TENTATIVE
+ Class: PRIVATE")
(icalendar-testsuite--test-import
"UID
LAST-MODIFIED
:20041118T013640Z
"
- "&2004/11/23 14:00-14:30 folded summary"
- "&23/11/2004 14:00-14:30 folded summary"
- "&11/23/2004 14:00-14:30 folded summary")
+ "&2004/11/23 14:00-14:30 folded summary
+ Status: TENTATIVE
+ Class: PRIVATE"
+ "&23/11/2004 14:00-14:30 folded summary
+ Status: TENTATIVE
+ Class: PRIVATE"
+ "&11/23/2004 14:00-14:30 folded summary
+ Status: TENTATIVE
+ Class: PRIVATE")
(icalendar-testsuite--test-import
"UID
:6161a312-3902-11d9-b512-f764153bb28b
DTSTAMP
:20041118T013641Z
"
- "&2004/11/23 14:45-15:45 another example"
- "&23/11/2004 14:45-15:45 another example"
- "&11/23/2004 14:45-15:45 another example")
+ "&2004/11/23 14:45-15:45 another example
+ Status: TENTATIVE
+ Class: PRIVATE"
+ "&23/11/2004 14:45-15:45 another example
+ Status: TENTATIVE
+ Class: PRIVATE"
+ "&11/23/2004 14:45-15:45 another example
+ Status: TENTATIVE
+ Class: PRIVATE")
(icalendar-testsuite--test-import
"SUMMARY:rrule daily
SEQUENCE:1
CREATED:20041127T183329
"
- "&%%(and (diary-cyclic 1 2001 12 21) (diary-block 2001 12 21 2001 12 29)) Urlaub"
- "&%%(and (diary-cyclic 1 21 12 2001) (diary-block 21 12 2001 29 12 2001)) Urlaub"
- "&%%(and (diary-cyclic 1 12 21 2001) (diary-block 12 21 2001 12 29 2001)) Urlaub")
- )
+ "&%%(and (diary-cyclic 1 2001 12 21) (diary-block 2001 12 21 2001 12 29)) Urlaub
+ Class: PUBLIC"
+ "&%%(and (diary-cyclic 1 21 12 2001) (diary-block 21 12 2001 29 12 2001)) Urlaub
+ Class: PUBLIC"
+ "&%%(and (diary-cyclic 1 12 21 2001) (diary-block 12 21 2001 12 29 2001)) Urlaub
+ Class: PUBLIC")
+
+ ;;bug#6766 -- multiple byday values in a weekly rrule
+ (icalendar-testsuite--test-import
+"CLASS:PUBLIC
+DTEND;TZID=America/New_York:20100421T120000
+DTSTAMP:20100525T141214Z
+DTSTART;TZID=America/New_York:20100421T113000
+RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,WE,TH,FR
+SEQUENCE:1
+STATUS:CONFIRMED
+SUMMARY:Scrum
+TRANSP:OPAQUE
+UID:8814e3f9-7482-408f-996c-3bfe486a1262
+END:VEVENT
+BEGIN:VEVENT
+CLASS:PUBLIC
+DTSTAMP:20100525T141214Z
+DTSTART;VALUE=DATE:20100422
+DTEND;VALUE=DATE:20100423
+RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=TU,TH
+SEQUENCE:1
+SUMMARY:Tues + Thurs thinking
+TRANSP:OPAQUE
+UID:8814e3f9-7482-408f-996c-3bfe486a1263
+"
+"&%%(and (memq (calendar-day-of-week date) '(1 3 4 5)) (diary-cyclic 1 2010 4 21)) 11:30-12:00 Scrum
+ Status: CONFIRMED
+ Class: PUBLIC
+&%%(and (memq (calendar-day-of-week date) '(2 4)) (diary-cyclic 1 2010 4 22)) Tues + Thurs thinking
+ Class: PUBLIC"
+
+"&%%(and (memq (calendar-day-of-week date) '(1 3 4 5)) (diary-cyclic 1 21 4 2010)) 11:30-12:00 Scrum
+ Status: CONFIRMED
+ Class: PUBLIC
+&%%(and (memq (calendar-day-of-week date) '(2 4)) (diary-cyclic 1 22 4 2010)) Tues + Thurs thinking
+ Class: PUBLIC"
+
+"&%%(and (memq (calendar-day-of-week date) '(1 3 4 5)) (diary-cyclic 1 4 21 2010)) 11:30-12:00 Scrum
+ Status: CONFIRMED
+ Class: PUBLIC
+&%%(and (memq (calendar-day-of-week date) '(2 4)) (diary-cyclic 1 4 22 2010)) Tues + Thurs thinking
+ Class: PUBLIC")
+)
;; ======================================================================
;; Export tests
"&9/5/2003 10:30-15:30 On-Site Interview
Desc: 10:30am - Blah
Location: Cccc
- Organizer: MAILTO:aaaaaaa@aaaaaaa.com"
+ Organizer: MAILTO:aaaaaaa@aaaaaaa.com
+ Status: CONFIRMED"
"&5/9/2003 10:30-15:30 On-Site Interview
Desc: 10:30am - Blah
Location: Cccc
- Organizer: MAILTO:aaaaaaa@aaaaaaa.com")
+ Organizer: MAILTO:aaaaaaa@aaaaaaa.com
+ Status: CONFIRMED")
;; 2003-06-18 a
(icalendar-testsuite--test-import
"&23/6/2003 11:00-12:00 Dress Rehearsal for XXXX-XXXX
Desc: 753 Zeichen hier radiert
Location: 555 or TN 555-5555 ID 5555 & NochWas (see below)
- Organizer: MAILTO:xxx@xxxxx.com"
+ Organizer: MAILTO:xxx@xxxxx.com
+ Status: CONFIRMED"
"&6/23/2003 11:00-12:00 Dress Rehearsal for XXXX-XXXX
Desc: 753 Zeichen hier radiert
Location: 555 or TN 555-5555 ID 5555 & NochWas (see below)
- Organizer: MAILTO:xxx@xxxxx.com")
+ Organizer: MAILTO:xxx@xxxxx.com
+ Status: CONFIRMED")
;; 2003-06-18 b -- uses timezone
(icalendar-testsuite--test-import
\(A-Americas,exgen1)\":MAILTO:bbb@bbbbb.com
LOCATION:123 or TN 123-1234 ID abcd & SonstWo (see below)
DTEND;TZID=\"Mountain Time (US & Canada)\":20030623T100000
-DESCRIPTION:Viele Zeichen standen hier früher
+DESCRIPTION:Viele Zeichen standen hier früher
SEQUENCE:0
PRIORITY:5
CLASS:
END:VCALENDAR"
nil
"&23/6/2003 17:00-18:00 Updated: Dress Rehearsal for ABC01-15
- Desc: Viele Zeichen standen hier früher
+ Desc: Viele Zeichen standen hier früher
Location: 123 or TN 123-1234 ID abcd & SonstWo (see below)
Organizer: MAILTO:bbb@bbbbb.com
Status: CONFIRMED"
"&6/23/2003 17:00-18:00 Updated: Dress Rehearsal for ABC01-15
- Desc: Viele Zeichen standen hier früher
+ Desc: Viele Zeichen standen hier früher
Location: 123 or TN 123-1234 ID abcd & SonstWo (see below)
Organizer: MAILTO:bbb@bbbbb.com
Status: CONFIRMED")
:20050128T011209Z"
nil
"&%%(and (diary-block 6 2 2005 6 2 2005)) Waitangi Day
- Desc: abcdef"
+ Desc: abcdef
+ Status: CONFIRMED
+ Class: PRIVATE"
"&%%(and (diary-block 2 6 2005 2 6 2005)) Waitangi Day
- Desc: abcdef")
+ Desc: abcdef
+ Status: CONFIRMED
+ Class: PRIVATE")
;; 2005-03-01 lt
(icalendar-testsuite--test-import