aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
authorHelmut Eller2026-02-13 09:10:16 +0100
committerHelmut Eller2026-02-13 09:10:16 +0100
commit91c9e9883488d715a30877dfd7641ef4b3c62658 (patch)
treee2c4525147e443f86baf9d0144aeadec082d7564 /test
parent9a4a54af9192a6653164364c75721ee814ffb1e8 (diff)
parentf1fe4d46190263e164ccd1e066095d46a156297f (diff)
downloademacs-feature/igc.tar.gz
emacs-feature/igc.zip
Merge branch 'master' into feature/igcfeature/igc
Diffstat (limited to 'test')
-rw-r--r--test/lisp/align-tests.el47
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-bug-11473.diary-american8
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-bug-11473.diary-european8
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-bug-11473.diary-iso8
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-bug-22092.diary-american6
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-bug-22092.diary-european6
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-bug-22092.diary-iso6
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-bug-24199.diary-all11
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-bug-33277.diary-american1
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-bug-33277.diary-european1
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-bug-33277.diary-iso1
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-bug-6766.diary-all12
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-duration-2.diary-all5
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-duration.diary-american1
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-duration.diary-european1
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-duration.diary-iso1
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-legacy-function.diary-all10
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-legacy-vars.diary-american8
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-legacy-vars.diary-european8
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-legacy-vars.diary-iso8
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-multiple-vcalendars.diary-american7
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-multiple-vcalendars.diary-european7
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-multiple-vcalendars.diary-iso7
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-non-recurring-1.diary-american1
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-non-recurring-1.diary-european1
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-non-recurring-1.diary-iso1
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-non-recurring-all-day.diary-american1
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-non-recurring-all-day.diary-european1
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-non-recurring-all-day.diary-iso1
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-non-recurring-another-example.diary-american4
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-non-recurring-another-example.diary-european4
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-non-recurring-another-example.diary-iso4
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-non-recurring-block.diary-american4
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-non-recurring-block.diary-european4
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-non-recurring-block.diary-iso4
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-non-recurring-folded-summary.diary-american4
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-non-recurring-folded-summary.diary-european4
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-non-recurring-folded-summary.diary-iso4
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-non-recurring-long-summary.diary-american1
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-non-recurring-long-summary.diary-european1
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-non-recurring-long-summary.diary-iso1
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-real-world-2003-05-29.diary-american10
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-real-world-2003-05-29.diary-european10
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-real-world-2003-06-18a.diary-american8
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-real-world-2003-06-18a.diary-european8
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-real-world-2003-06-18b.diary-american6
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-real-world-2003-06-18b.diary-european5
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-real-world-2004-11-19.diary-american28
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-real-world-2004-11-19.diary-european28
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-real-world-2005-02-07.diary-american6
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-real-world-2005-02-07.diary-european6
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-real-world-2005-03-01.diary-american2
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-real-world-2005-03-01.diary-european2
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-real-world-no-dst.diary-american4
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-real-world-no-dst.diary-european4
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-rrule-anniversary.diary-all1
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-rrule-count-bi-weekly.diary-all3
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-rrule-count-daily-long.diary-all3
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-rrule-count-daily-short.diary-all3
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-rrule-count-every-second-month.diary-all3
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-rrule-count-every-second-year.diary-all3
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-rrule-count-monthly.diary-all3
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-rrule-count-yearly.diary-all3
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-rrule-daily-two-day.diary-all3
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-rrule-daily-with-exceptions.diary-all4
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-rrule-daily.diary-all2
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-rrule-monthly-no-end.diary-all3
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-rrule-monthly-with-end.diary-all3
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-rrule-weekly.diary-all2
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-rrule-yearly.diary-all3
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-time-format-12hr-blank.diary-iso1
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-with-attachment.diary-iso3
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-with-timezone.diary-iso3
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-with-uid.diary-american2
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-with-uid.diary-european2
-rw-r--r--test/lisp/calendar/diary-icalendar-resources/import-with-uid.diary-iso2
-rw-r--r--test/lisp/calendar/diary-icalendar-tests.el1278
-rw-r--r--test/lisp/calendar/icalendar-ast-tests.el112
-rw-r--r--test/lisp/calendar/icalendar-parser-tests.el2032
-rw-r--r--test/lisp/calendar/icalendar-recur-tests.el2880
-rw-r--r--test/lisp/calendar/icalendar-resources/import-legacy-function.ics16
-rw-r--r--test/lisp/calendar/icalendar-resources/import-legacy-vars.ics16
-rw-r--r--test/lisp/calendar/icalendar-resources/import-non-recurring-all-day.ics17
-rw-r--r--test/lisp/calendar/icalendar-resources/import-rrule-anniversary.ics21
-rw-r--r--test/lisp/calendar/icalendar-resources/import-rrule-daily-with-exceptions.ics23
-rw-r--r--test/lisp/calendar/icalendar-resources/import-rrule-daily.ics22
-rw-r--r--test/lisp/calendar/icalendar-resources/import-rrule-monthly-no-end.ics22
-rw-r--r--test/lisp/calendar/icalendar-resources/import-rrule-monthly-with-end.ics21
-rw-r--r--test/lisp/calendar/icalendar-resources/import-rrule-weekly.ics22
-rw-r--r--test/lisp/calendar/icalendar-resources/import-time-format-12hr-blank.ics9
-rw-r--r--test/lisp/calendar/icalendar-resources/import-with-attachment.ics11
-rw-r--r--test/lisp/calendar/icalendar-resources/import-with-timezone.ics54
-rw-r--r--test/lisp/calendar/icalendar-tests.el119
-rw-r--r--test/lisp/misc-tests.el90
-rw-r--r--test/lisp/net/dbus-tests.el174
-rw-r--r--test/lisp/net/tramp-archive-tests.el6
-rw-r--r--test/lisp/net/tramp-tests.el52
-rw-r--r--test/lisp/progmodes/eglot-tests.el3
-rw-r--r--test/lisp/vc/ediff-mult-tests.el5
-rw-r--r--test/lisp/vc/vc-git-tests.el35
-rw-r--r--test/src/buffer-tests.el11
-rw-r--r--test/src/data-tests.el34
102 files changed, 7258 insertions, 232 deletions
diff --git a/test/lisp/align-tests.el b/test/lisp/align-tests.el
index 92605a7f4aa..43660f8de87 100644
--- a/test/lisp/align-tests.el
+++ b/test/lisp/align-tests.el
@@ -36,6 +36,53 @@
36 (ert-test-erts-file (ert-resource-file "c-mode.erts") 36 (ert-test-erts-file (ert-resource-file "c-mode.erts")
37 (test-align-transform-fun #'c-mode))) 37 (test-align-transform-fun #'c-mode)))
38 38
39(ert-deftest align-c-multi-section ()
40 "Test alignment of multiple sections in C code.
41Regression test for bug where positions become stale after earlier
42sections are aligned, causing incorrect alignment in later sections."
43 (let ((input "int main(void)
44{
45 long signed int foo = 5;
46 int bar = 7;
47 {
48 int a1 = 4;
49 int b1 = 2;
50 long signed int junk1 = 2;
51 }
52 {
53 int a2 = 4; /* comment */
54 int b2 = 2;
55 long signed int junk2 = 2; /* another comment */
56 }
57
58 return 0;
59}
60")
61 (expected "int main(void)
62{
63 long signed int foo = 5;
64 int bar = 7;
65 {
66 int a1 = 4;
67 int b1 = 2;
68 long signed int junk1 = 2;
69 }
70 {
71 int a2 = 4; /* comment */
72 int b2 = 2;
73 long signed int junk2 = 2; /* another comment */
74 }
75
76 return 0;
77}
78"))
79 (with-temp-buffer
80 (c-mode)
81 (setq indent-tabs-mode nil)
82 (insert input)
83 (align (point-min) (point-max))
84 (should (equal (buffer-string) expected)))))
85
39(ert-deftest align-css () 86(ert-deftest align-css ()
40 (let ((indent-tabs-mode nil)) 87 (let ((indent-tabs-mode nil))
41 (ert-test-erts-file (ert-resource-file "css-mode.erts") 88 (ert-test-erts-file (ert-resource-file "css-mode.erts")
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-bug-11473.diary-american b/test/lisp/calendar/diary-icalendar-resources/import-bug-11473.diary-american
new file mode 100644
index 00000000000..3f810d31f3b
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-bug-11473.diary-american
@@ -0,0 +1,8 @@
1&5/15/2012 15:00-15:30 Query
2 Location: phone
3 Status: confirmed
4 Organizer: A. Luser <a.luser@foo.com>
5 Attendee: Luser, Other <other.luser@foo.com> (needs-action)
6 Access: public
7 UID: 040000008200E00074C5B7101A82E0080000000020FFAED0CFEFCC01000000000000000010000000575268034ECDB649A15349B1BF240F15
8 Description: Whassup?
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-bug-11473.diary-european b/test/lisp/calendar/diary-icalendar-resources/import-bug-11473.diary-european
new file mode 100644
index 00000000000..0555ea90364
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-bug-11473.diary-european
@@ -0,0 +1,8 @@
1&15/5/2012 15:00-15:30 Query
2 Location: phone
3 Status: confirmed
4 Organizer: A. Luser <a.luser@foo.com>
5 Attendee: Luser, Other <other.luser@foo.com> (needs-action)
6 Access: public
7 UID: 040000008200E00074C5B7101A82E0080000000020FFAED0CFEFCC01000000000000000010000000575268034ECDB649A15349B1BF240F15
8 Description: Whassup?
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-bug-11473.diary-iso b/test/lisp/calendar/diary-icalendar-resources/import-bug-11473.diary-iso
new file mode 100644
index 00000000000..0e3a3fb8715
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-bug-11473.diary-iso
@@ -0,0 +1,8 @@
1&2012/5/15 15:00-15:30 Query
2 Location: phone
3 Status: confirmed
4 Organizer: A. Luser <a.luser@foo.com>
5 Attendee: Luser, Other <other.luser@foo.com> (needs-action)
6 Access: public
7 UID: 040000008200E00074C5B7101A82E0080000000020FFAED0CFEFCC01000000000000000010000000575268034ECDB649A15349B1BF240F15
8 Description: Whassup?
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-bug-22092.diary-american b/test/lisp/calendar/diary-icalendar-resources/import-bug-22092.diary-american
new file mode 100644
index 00000000000..d7866ddb899
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-bug-22092.diary-american
@@ -0,0 +1,6 @@
1&12/8/2014 18:30-22:55 Norwegian til Tromsoe-Langnes -
2 Location: Stavanger-Sola
3 Category: Appointment
4 Access: public
5 UID: RFCALITEM1
6 Description: Fly med Norwegian, reservasjon. Fra Stavanger til Troms&#248; 8. des 2014 18:30, DY545Fly med Norwegian, reservasjon . Fra Stavanger til Troms&#248; 8. des 2014 21:00, DY390 \ No newline at end of file
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-bug-22092.diary-european b/test/lisp/calendar/diary-icalendar-resources/import-bug-22092.diary-european
new file mode 100644
index 00000000000..ce9933aa5f8
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-bug-22092.diary-european
@@ -0,0 +1,6 @@
1&8/12/2014 18:30-22:55 Norwegian til Tromsoe-Langnes -
2 Location: Stavanger-Sola
3 Category: Appointment
4 Access: public
5 UID: RFCALITEM1
6 Description: Fly med Norwegian, reservasjon. Fra Stavanger til Troms&#248; 8. des 2014 18:30, DY545Fly med Norwegian, reservasjon . Fra Stavanger til Troms&#248; 8. des 2014 21:00, DY390 \ No newline at end of file
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-bug-22092.diary-iso b/test/lisp/calendar/diary-icalendar-resources/import-bug-22092.diary-iso
new file mode 100644
index 00000000000..9c45f848e76
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-bug-22092.diary-iso
@@ -0,0 +1,6 @@
1&2014/12/8 18:30-22:55 Norwegian til Tromsoe-Langnes -
2 Location: Stavanger-Sola
3 Category: Appointment
4 Access: public
5 UID: RFCALITEM1
6 Description: Fly med Norwegian, reservasjon. Fra Stavanger til Troms&#248; 8. des 2014 18:30, DY545Fly med Norwegian, reservasjon . Fra Stavanger til Troms&#248; 8. des 2014 21:00, DY390 \ No newline at end of file
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-bug-24199.diary-all b/test/lisp/calendar/diary-icalendar-resources/import-bug-24199.diary-all
new file mode 100644
index 00000000000..cf3e5884710
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-bug-24199.diary-all
@@ -0,0 +1,11 @@
1&%%(diary-rrule :rule '((FREQ MONTHLY) (BYDAY ((3 . 1))) (INTERVAL 1))
2 :exclude
3 '((0 46 11 6 1 2016 3 -1 0) (0 46 11 3 2 2016 3 -1 0)
4 (0 46 11 2 3 2016 3 -1 0) (0 46 10 4 5 2016 3 -1 0)
5 (0 46 10 1 6 2016 3 -1 0))
6 :start '(0 46 12 2 12 2015 3 -1 nil) :duration
7 '(0 14 3 0 nil nil nil -1 nil)) Summary
8 Location: Loc
9 Access: private
10 UID: 9188710a-08a7-4061-bae3-d4cf4972599a
11 Description: Desc
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-bug-33277.diary-american b/test/lisp/calendar/diary-icalendar-resources/import-bug-33277.diary-american
new file mode 100644
index 00000000000..c546fa9a97c
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-bug-33277.diary-american
@@ -0,0 +1 @@
&11/5/2018 21:00 event with same start/end time
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-bug-33277.diary-european b/test/lisp/calendar/diary-icalendar-resources/import-bug-33277.diary-european
new file mode 100644
index 00000000000..28e53960536
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-bug-33277.diary-european
@@ -0,0 +1 @@
&5/11/2018 21:00 event with same start/end time
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-bug-33277.diary-iso b/test/lisp/calendar/diary-icalendar-resources/import-bug-33277.diary-iso
new file mode 100644
index 00000000000..faa7aeafeb5
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-bug-33277.diary-iso
@@ -0,0 +1 @@
&2018/11/5 21:00 event with same start/end time
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-bug-6766.diary-all b/test/lisp/calendar/diary-icalendar-resources/import-bug-6766.diary-all
new file mode 100644
index 00000000000..4e1b69158c9
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-bug-6766.diary-all
@@ -0,0 +1,12 @@
1&%%(diary-rrule :rule '((FREQ WEEKLY) (INTERVAL 1) (BYDAY (1 3 4 5)))
2 :start '(0 30 11 21 4 2010 3 -1 nil) :duration
3 '(0 30 0 0 nil nil nil -1 nil)) Scrum
4 Status: confirmed
5 Access: public
6 UID: 8814e3f9-7482-408f-996c-3bfe486a1262
7
8&%%(diary-rrule :rule '((FREQ WEEKLY) (INTERVAL 1) (BYDAY (2 4))) :start
9 '(4 22 2010) :duration
10 '(nil nil nil 1 nil nil nil -1 nil)) Tues + Thurs thinking
11 Access: public
12 UID: 8814e3f9-7482-408f-996c-3bfe486a1263
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-duration-2.diary-all b/test/lisp/calendar/diary-icalendar-resources/import-duration-2.diary-all
new file mode 100644
index 00000000000..5d018c1cb6e
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-duration-2.diary-all
@@ -0,0 +1,5 @@
1&%%(diary-rrule :rule
2 '((FREQ DAILY) (UNTIL (12 29 2001)) (INTERVAL 1) (WKST 0))
3 :start '(12 21 2001)) Urlaub
4 Access: public
5 UID: 20041127T183329Z-18215-1001-4536-49109@andromeda
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-duration.diary-american b/test/lisp/calendar/diary-icalendar-resources/import-duration.diary-american
new file mode 100644
index 00000000000..b44b4aed72c
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-duration.diary-american
@@ -0,0 +1 @@
&%%(diary-block 2 17 2005 2 23 2005) duration
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-duration.diary-european b/test/lisp/calendar/diary-icalendar-resources/import-duration.diary-european
new file mode 100644
index 00000000000..caee11e0a38
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-duration.diary-european
@@ -0,0 +1 @@
&%%(diary-block 17 2 2005 23 2 2005) duration
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-duration.diary-iso b/test/lisp/calendar/diary-icalendar-resources/import-duration.diary-iso
new file mode 100644
index 00000000000..573121644d5
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-duration.diary-iso
@@ -0,0 +1 @@
&%%(diary-block 2005 2 17 2005 2 23) duration
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-legacy-function.diary-all b/test/lisp/calendar/diary-icalendar-resources/import-legacy-function.diary-all
new file mode 100644
index 00000000000..e0d27f4d1b0
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-legacy-function.diary-all
@@ -0,0 +1,10 @@
1 SUMMARY: Testing legacy `icalendar-import-format' function
2 DESCRIPTION: described
3 CLASS: private
4 LOCATION: somewhere
5 ORGANIZER: mailto:baz@example.com
6 STATUS: CONFIRMED
7 URL: http://example.com/foo/baz
8 UID: some-unique-id-here
9 DTSTART: 20250919T090000
10 DTEND: 20250919T113000
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-legacy-vars.diary-american b/test/lisp/calendar/diary-icalendar-resources/import-legacy-vars.diary-american
new file mode 100644
index 00000000000..42076a32138
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-legacy-vars.diary-american
@@ -0,0 +1,8 @@
19/19/2025 09:00-11:30 Testing legacy `icalendar-import-format*' vars
2 CLASS=private
3 DESCRIPTION=described
4 LOCATION=somewhere
5 ORGANIZER=mailto:baz@example.com
6 STATUS=confirmed
7 URL=http://example.com/foo/baz
8 UID=some-unique-id-here \ No newline at end of file
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-legacy-vars.diary-european b/test/lisp/calendar/diary-icalendar-resources/import-legacy-vars.diary-european
new file mode 100644
index 00000000000..699c627e2f9
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-legacy-vars.diary-european
@@ -0,0 +1,8 @@
119/9/2025 09:00-11:30 Testing legacy `icalendar-import-format*' vars
2 CLASS=private
3 DESCRIPTION=described
4 LOCATION=somewhere
5 ORGANIZER=mailto:baz@example.com
6 STATUS=confirmed
7 URL=http://example.com/foo/baz
8 UID=some-unique-id-here \ No newline at end of file
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-legacy-vars.diary-iso b/test/lisp/calendar/diary-icalendar-resources/import-legacy-vars.diary-iso
new file mode 100644
index 00000000000..f6d69805c19
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-legacy-vars.diary-iso
@@ -0,0 +1,8 @@
12025/9/19 09:00-11:30 Testing legacy `icalendar-import-format*' vars
2 CLASS=private
3 DESCRIPTION=described
4 LOCATION=somewhere
5 ORGANIZER=mailto:baz@example.com
6 STATUS=confirmed
7 URL=http://example.com/foo/baz
8 UID=some-unique-id-here \ No newline at end of file
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-multiple-vcalendars.diary-american b/test/lisp/calendar/diary-icalendar-resources/import-multiple-vcalendars.diary-american
new file mode 100644
index 00000000000..ef28f1abfc1
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-multiple-vcalendars.diary-american
@@ -0,0 +1,7 @@
1&7/23/2011 event-1
2
3&7/24/2011 event-2
4
5&7/25/2011 event-3a
6
7&7/25/2011 event-3b
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-multiple-vcalendars.diary-european b/test/lisp/calendar/diary-icalendar-resources/import-multiple-vcalendars.diary-european
new file mode 100644
index 00000000000..db9625f390d
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-multiple-vcalendars.diary-european
@@ -0,0 +1,7 @@
1&23/7/2011 event-1
2
3&24/7/2011 event-2
4
5&25/7/2011 event-3a
6
7&25/7/2011 event-3b
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-multiple-vcalendars.diary-iso b/test/lisp/calendar/diary-icalendar-resources/import-multiple-vcalendars.diary-iso
new file mode 100644
index 00000000000..bbe009c200e
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-multiple-vcalendars.diary-iso
@@ -0,0 +1,7 @@
1&2011/7/23 event-1
2
3&2011/7/24 event-2
4
5&2011/7/25 event-3a
6
7&2011/7/25 event-3b
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-1.diary-american b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-1.diary-american
new file mode 100644
index 00000000000..780e3a8ce64
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-1.diary-american
@@ -0,0 +1 @@
&9/19/2003 09:00-11:30 non-recurring
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-1.diary-european b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-1.diary-european
new file mode 100644
index 00000000000..7e0cd21b784
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-1.diary-european
@@ -0,0 +1 @@
&19/9/2003 09:00-11:30 non-recurring
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-1.diary-iso b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-1.diary-iso
new file mode 100644
index 00000000000..c7311286619
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-1.diary-iso
@@ -0,0 +1 @@
&2003/9/19 09:00-11:30 non-recurring
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-all-day.diary-american b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-all-day.diary-american
new file mode 100644
index 00000000000..1d4bb6a337e
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-all-day.diary-american
@@ -0,0 +1 @@
&9/19/2003 non-recurring allday
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-all-day.diary-european b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-all-day.diary-european
new file mode 100644
index 00000000000..b56c7f4e17f
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-all-day.diary-european
@@ -0,0 +1 @@
&19/9/2003 non-recurring allday
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-all-day.diary-iso b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-all-day.diary-iso
new file mode 100644
index 00000000000..f1c70ab34c3
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-all-day.diary-iso
@@ -0,0 +1 @@
&2003/9/19 non-recurring allday
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-another-example.diary-american b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-another-example.diary-american
new file mode 100644
index 00000000000..847e7cf6cab
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-another-example.diary-american
@@ -0,0 +1,4 @@
1&11/23/2004 14:45-15:45 another example
2 Status: tentative
3 Access: private
4 UID: 6161a312-3902-11d9-b512-f764153bb28b
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-another-example.diary-european b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-another-example.diary-european
new file mode 100644
index 00000000000..5c70e58f4d0
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-another-example.diary-european
@@ -0,0 +1,4 @@
1&23/11/2004 14:45-15:45 another example
2 Status: tentative
3 Access: private
4 UID: 6161a312-3902-11d9-b512-f764153bb28b
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-another-example.diary-iso b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-another-example.diary-iso
new file mode 100644
index 00000000000..d663965404b
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-another-example.diary-iso
@@ -0,0 +1,4 @@
1&2004/11/23 14:45-15:45 another example
2 Status: tentative
3 Access: private
4 UID: 6161a312-3902-11d9-b512-f764153bb28b
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-block.diary-american b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-block.diary-american
new file mode 100644
index 00000000000..c795ebf2abc
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-block.diary-american
@@ -0,0 +1,4 @@
1&%%(diary-block 7 19 2004 8 27 2004) Sommerferien
2 Status: tentative
3 Access: private
4 UID: 748f2da0-0d9b-11d8-97af-b4ec8686ea61
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-block.diary-european b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-block.diary-european
new file mode 100644
index 00000000000..4d6b71600a8
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-block.diary-european
@@ -0,0 +1,4 @@
1&%%(diary-block 19 7 2004 27 8 2004) Sommerferien
2 Status: tentative
3 Access: private
4 UID: 748f2da0-0d9b-11d8-97af-b4ec8686ea61
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-block.diary-iso b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-block.diary-iso
new file mode 100644
index 00000000000..f6d23b049ed
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-block.diary-iso
@@ -0,0 +1,4 @@
1&%%(diary-block 2004 7 19 2004 8 27) Sommerferien
2 Status: tentative
3 Access: private
4 UID: 748f2da0-0d9b-11d8-97af-b4ec8686ea61
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-folded-summary.diary-american b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-folded-summary.diary-american
new file mode 100644
index 00000000000..a86f560fb08
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-folded-summary.diary-american
@@ -0,0 +1,4 @@
1&11/23/2004 14:00-14:30 folded summary
2 Status: tentative
3 Access: private
4 UID: 04979712-3902-11d9-93dd-8f9f4afe08da
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-folded-summary.diary-european b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-folded-summary.diary-european
new file mode 100644
index 00000000000..0c5e640a615
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-folded-summary.diary-european
@@ -0,0 +1,4 @@
1&23/11/2004 14:00-14:30 folded summary
2 Status: tentative
3 Access: private
4 UID: 04979712-3902-11d9-93dd-8f9f4afe08da
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-folded-summary.diary-iso b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-folded-summary.diary-iso
new file mode 100644
index 00000000000..699358dc504
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-folded-summary.diary-iso
@@ -0,0 +1,4 @@
1&2004/11/23 14:00-14:30 folded summary
2 Status: tentative
3 Access: private
4 UID: 04979712-3902-11d9-93dd-8f9f4afe08da
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-long-summary.diary-american b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-long-summary.diary-american
new file mode 100644
index 00000000000..84cd464c568
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-long-summary.diary-american
@@ -0,0 +1 @@
&9/19/2003 long summary
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-long-summary.diary-european b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-long-summary.diary-european
new file mode 100644
index 00000000000..5d6524202c3
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-long-summary.diary-european
@@ -0,0 +1 @@
&19/9/2003 long summary
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-long-summary.diary-iso b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-long-summary.diary-iso
new file mode 100644
index 00000000000..d2300522d9a
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-non-recurring-long-summary.diary-iso
@@ -0,0 +1 @@
&2003/9/19 long summary
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-real-world-2003-05-29.diary-american b/test/lisp/calendar/diary-icalendar-resources/import-real-world-2003-05-29.diary-american
new file mode 100644
index 00000000000..53711dc68ed
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-real-world-2003-05-29.diary-american
@@ -0,0 +1,10 @@
1&5/9/2003 10:30-15:30 On-Site Interview
2 Location: Cccc
3 Status: confirmed
4 Organizer: Aaaaaa Aaaaa <aaaaaaa@aaaaaaa.com>
5 Attendees:
6 Xxxxxxxx Xxxxxxxxxxxx <xxxxxxxx@xxxxxxx.com> (needs-action)
7 Yyyyyyy Yyyyy <yyyyyyy@yyyyyyy.com> (needs-action)
8 Zzzz Zzzzzz <zzzzzz@zzzzzzz.com> (needs-action)
9 UID: 040000008200E00074C5B7101A82E0080000000080B6DE661216C301000000000000000010000000DB823520692542408ED02D7023F9DFF9
10 Description: 10:30am - Blah
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-real-world-2003-05-29.diary-european b/test/lisp/calendar/diary-icalendar-resources/import-real-world-2003-05-29.diary-european
new file mode 100644
index 00000000000..17efbd64c18
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-real-world-2003-05-29.diary-european
@@ -0,0 +1,10 @@
1&9/5/2003 10:30-15:30 On-Site Interview
2 Location: Cccc
3 Status: confirmed
4 Organizer: Aaaaaa Aaaaa <aaaaaaa@aaaaaaa.com>
5 Attendees:
6 Xxxxxxxx Xxxxxxxxxxxx <xxxxxxxx@xxxxxxx.com> (needs-action)
7 Yyyyyyy Yyyyy <yyyyyyy@yyyyyyy.com> (needs-action)
8 Zzzz Zzzzzz <zzzzzz@zzzzzzz.com> (needs-action)
9 UID: 040000008200E00074C5B7101A82E0080000000080B6DE661216C301000000000000000010000000DB823520692542408ED02D7023F9DFF9
10 Description: 10:30am - Blah
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-real-world-2003-06-18a.diary-american b/test/lisp/calendar/diary-icalendar-resources/import-real-world-2003-06-18a.diary-american
new file mode 100644
index 00000000000..1b819404abf
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-real-world-2003-06-18a.diary-american
@@ -0,0 +1,8 @@
1&6/23/2003 11:00-12:00 Dress Rehearsal for XXXX-XXXX
2 Location: 555 or TN 555-5555 ID 5555 & NochWas (see below)
3 Status: confirmed
4 Organizer: ABCD,TECHTRAINING(A-Americas,exgen1) <xxx@xxxxx.com>
5 Attendee:
6 AAAAA,AAAAA (A-AAAAAAA,ex1) <aaaaa_aaaaa@aaaaa.com> (needs-action)
7 UID: 040000008200E00074C5B7101A82E00800000000608AA7DA9835C3010000000000000000100000007C3A6D65EE726E40B7F3D69A23BD567E
8 Description: 753 Zeichen hier radiert
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-real-world-2003-06-18a.diary-european b/test/lisp/calendar/diary-icalendar-resources/import-real-world-2003-06-18a.diary-european
new file mode 100644
index 00000000000..379c612419f
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-real-world-2003-06-18a.diary-european
@@ -0,0 +1,8 @@
1&23/6/2003 11:00-12:00 Dress Rehearsal for XXXX-XXXX
2 Location: 555 or TN 555-5555 ID 5555 & NochWas (see below)
3 Status: confirmed
4 Organizer: ABCD,TECHTRAINING(A-Americas,exgen1) <xxx@xxxxx.com>
5 Attendee:
6 AAAAA,AAAAA (A-AAAAAAA,ex1) <aaaaa_aaaaa@aaaaa.com> (needs-action)
7 UID: 040000008200E00074C5B7101A82E00800000000608AA7DA9835C3010000000000000000100000007C3A6D65EE726E40B7F3D69A23BD567E
8 Description: 753 Zeichen hier radiert
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-real-world-2003-06-18b.diary-american b/test/lisp/calendar/diary-icalendar-resources/import-real-world-2003-06-18b.diary-american
new file mode 100644
index 00000000000..0e34b5d7fa9
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-real-world-2003-06-18b.diary-american
@@ -0,0 +1,6 @@
1&6/23/2003 17:00-18:00 Updated: Dress Rehearsal for ABC01-15
2 Desc: Viele Zeichen standen hier früher
3 Location: 123 or TN 123-1234 ID abcd & SonstWo (see below)
4 Organizer: MAILTO:bbb@bbbbb.com
5 Status: CONFIRMED
6 UID: 040000008200E00074C5B7101A82E00800000000608AA7DA9835C3010000000000000000100000007C3A6D65EE726E40B7F3D69A23BD567E \ No newline at end of file
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-real-world-2003-06-18b.diary-european b/test/lisp/calendar/diary-icalendar-resources/import-real-world-2003-06-18b.diary-european
new file mode 100644
index 00000000000..e6151c78dce
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-real-world-2003-06-18b.diary-european
@@ -0,0 +1,5 @@
1&23/6/2003 09:00-10:00 Updated: Dress Rehearsal for ABC01-15
2 Location: 123 or TN 123-1234 ID abcd & SonstWo (see below)
3 Status: confirmed
4 UID: 040000008200E00074C5B7101A82E00800000000608AA7DA9835C3010000000000000000100000007C3A6D65EE726E40B7F3D69A23BD567E
5 Description: Viele Zeichen standen hier früher \ No newline at end of file
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-real-world-2004-11-19.diary-american b/test/lisp/calendar/diary-icalendar-resources/import-real-world-2004-11-19.diary-american
new file mode 100644
index 00000000000..17dff899314
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-real-world-2004-11-19.diary-american
@@ -0,0 +1,28 @@
1&%%(diary-rrule :rule '((FREQ WEEKLY) (INTERVAL 1) (BYDAY (1))) :start
2 '(11 1 2004) :duration
3 '(nil nil nil 1 nil nil nil -1 nil)) Wwww aa hhhh
4 Status: tentative
5 Access: private
6
7&%%(diary-rrule :rule '((FREQ WEEKLY) (INTERVAL 2) (BYDAY (5))) :start
8 '(0 0 14 12 11 2004 5 -1 nil) :duration
9 '(0 30 4 0 nil nil nil -1 nil)) MMM Aaaaaaaaa
10 Status: tentative
11 Access: private
12
13&%%(diary-block 11 19 2004 11 19 2004) Rrrr/Cccccc ii Aaaaaaaa
14 Status: tentative
15 Access: private
16 Description: Vvvvv Rrrr aaa Cccccc
17
18&11/23/2004 11:00-12:00 Hhhhhhhh
19 Status: tentative
20 Access: private
21
22&11/23/2004 14:00-14:30 Jjjjj & Wwwww
23 Status: tentative
24 Access: private
25
26&11/23/2004 14:45-15:45 BB Aaaaaaaa Bbbbb
27 Status: tentative
28 Access: private
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-real-world-2004-11-19.diary-european b/test/lisp/calendar/diary-icalendar-resources/import-real-world-2004-11-19.diary-european
new file mode 100644
index 00000000000..cfd3a43d55c
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-real-world-2004-11-19.diary-european
@@ -0,0 +1,28 @@
1&%%(diary-rrule :rule '((FREQ WEEKLY) (INTERVAL 1) (BYDAY (1))) :start
2 '(11 1 2004) :duration
3 '(nil nil nil 1 nil nil nil -1 nil)) Wwww aa hhhh
4 Status: tentative
5 Access: private
6
7&%%(diary-rrule :rule '((FREQ WEEKLY) (INTERVAL 2) (BYDAY (5))) :start
8 '(0 0 14 12 11 2004 5 -1 nil) :duration
9 '(0 30 4 0 nil nil nil -1 nil)) MMM Aaaaaaaaa
10 Status: tentative
11 Access: private
12
13&%%(diary-block 19 11 2004 19 11 2004) Rrrr/Cccccc ii Aaaaaaaa
14 Status: tentative
15 Access: private
16 Description: Vvvvv Rrrr aaa Cccccc
17
18&23/11/2004 11:00-12:00 Hhhhhhhh
19 Status: tentative
20 Access: private
21
22&23/11/2004 14:00-14:30 Jjjjj & Wwwww
23 Status: tentative
24 Access: private
25
26&23/11/2004 14:45-15:45 BB Aaaaaaaa Bbbbb
27 Status: tentative
28 Access: private
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-real-world-2005-02-07.diary-american b/test/lisp/calendar/diary-icalendar-resources/import-real-world-2005-02-07.diary-american
new file mode 100644
index 00000000000..f7518918f30
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-real-world-2005-02-07.diary-american
@@ -0,0 +1,6 @@
1&%%(diary-block 2 6 2005 2 6 2005) Waitangi Day
2 Status: confirmed
3 Category: Public Holiday
4 Access: private
5 UID: b60d398e-1dd1-11b2-a159-cf8cb05139f4
6 Description: abcdef
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-real-world-2005-02-07.diary-european b/test/lisp/calendar/diary-icalendar-resources/import-real-world-2005-02-07.diary-european
new file mode 100644
index 00000000000..c65526a5551
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-real-world-2005-02-07.diary-european
@@ -0,0 +1,6 @@
1&%%(diary-block 6 2 2005 6 2 2005) Waitangi Day
2 Status: confirmed
3 Category: Public Holiday
4 Access: private
5 UID: b60d398e-1dd1-11b2-a159-cf8cb05139f4
6 Description: abcdef
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-real-world-2005-03-01.diary-american b/test/lisp/calendar/diary-icalendar-resources/import-real-world-2005-03-01.diary-american
new file mode 100644
index 00000000000..9290254f1ce
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-real-world-2005-03-01.diary-american
@@ -0,0 +1,2 @@
1&%%(diary-block 2 17 2005 2 23 2005) Hhhhhh Aaaaa ii Aaaaaaaa
2 UID: 6AFA7558-6994-11D9-8A3A-000A95A0E830-RID
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-real-world-2005-03-01.diary-european b/test/lisp/calendar/diary-icalendar-resources/import-real-world-2005-03-01.diary-european
new file mode 100644
index 00000000000..61cbbc726d3
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-real-world-2005-03-01.diary-european
@@ -0,0 +1,2 @@
1&%%(diary-block 17 2 2005 23 2 2005) Hhhhhh Aaaaa ii Aaaaaaaa
2 UID: 6AFA7558-6994-11D9-8A3A-000A95A0E830-RID
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-real-world-no-dst.diary-american b/test/lisp/calendar/diary-icalendar-resources/import-real-world-no-dst.diary-american
new file mode 100644
index 00000000000..6c1d6667e63
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-real-world-no-dst.diary-american
@@ -0,0 +1,4 @@
1&11/16/2014 07:00-08:00 NoDST
2 Location: Everywhere
3 UID: 20141116T171439Z-678877132@marudot.com
4 Description: Test event from timezone without DST
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-real-world-no-dst.diary-european b/test/lisp/calendar/diary-icalendar-resources/import-real-world-no-dst.diary-european
new file mode 100644
index 00000000000..b710b4c61e5
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-real-world-no-dst.diary-european
@@ -0,0 +1,4 @@
1&16/11/2014 07:00-08:00 NoDST
2 Location: Everywhere
3 UID: 20141116T171439Z-678877132@marudot.com
4 Description: Test event from timezone without DST
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-rrule-anniversary.diary-all b/test/lisp/calendar/diary-icalendar-resources/import-rrule-anniversary.diary-all
new file mode 100644
index 00000000000..ce87095c080
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-rrule-anniversary.diary-all
@@ -0,0 +1 @@
&%%(diary-rrule :rule '((FREQ YEARLY)) :start '(8 15 2004)) Maria Himmelfahrt
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-rrule-count-bi-weekly.diary-all b/test/lisp/calendar/diary-icalendar-resources/import-rrule-count-bi-weekly.diary-all
new file mode 100644
index 00000000000..9c5cb38ddb8
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-rrule-count-bi-weekly.diary-all
@@ -0,0 +1,3 @@
1&%%(diary-rrule :rule '((FREQ WEEKLY) (COUNT 3) (INTERVAL 2)) :start
2 '(0 0 9 19 9 2003 5 -1 nil) :duration
3 '(0 30 2 0 nil nil nil -1 nil)) rrule count bi-weekly 3 times \ No newline at end of file
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-rrule-count-daily-long.diary-all b/test/lisp/calendar/diary-icalendar-resources/import-rrule-count-daily-long.diary-all
new file mode 100644
index 00000000000..3b7b4532506
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-rrule-count-daily-long.diary-all
@@ -0,0 +1,3 @@
1&%%(diary-rrule :rule '((FREQ DAILY) (COUNT 14) (INTERVAL 1)) :start
2 '(0 0 9 19 9 2003 5 -1 nil) :duration
3 '(0 30 2 0 nil nil nil -1 nil)) rrule count daily long
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-rrule-count-daily-short.diary-all b/test/lisp/calendar/diary-icalendar-resources/import-rrule-count-daily-short.diary-all
new file mode 100644
index 00000000000..08051f0d015
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-rrule-count-daily-short.diary-all
@@ -0,0 +1,3 @@
1&%%(diary-rrule :rule '((FREQ DAILY) (COUNT 1) (INTERVAL 1)) :start
2 '(0 0 9 19 9 2003 5 -1 nil) :duration
3 '(0 30 2 0 nil nil nil -1 nil)) rrule count daily short
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-rrule-count-every-second-month.diary-all b/test/lisp/calendar/diary-icalendar-resources/import-rrule-count-every-second-month.diary-all
new file mode 100644
index 00000000000..e5063cfed3b
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-rrule-count-every-second-month.diary-all
@@ -0,0 +1,3 @@
1&%%(diary-rrule :rule '((FREQ MONTHLY) (INTERVAL 2) (COUNT 5)) :start
2 '(0 0 9 19 9 2003 5 -1 nil) :duration
3 '(0 30 2 0 nil nil nil -1 nil)) rrule count every second month
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-rrule-count-every-second-year.diary-all b/test/lisp/calendar/diary-icalendar-resources/import-rrule-count-every-second-year.diary-all
new file mode 100644
index 00000000000..74fbc93986c
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-rrule-count-every-second-year.diary-all
@@ -0,0 +1,3 @@
1&%%(diary-rrule :rule '((FREQ YEARLY) (INTERVAL 2) (COUNT 5)) :start
2 '(0 0 9 19 9 2003 5 -1 nil) :duration
3 '(0 30 2 0 nil nil nil -1 nil)) rrule count every second year
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-rrule-count-monthly.diary-all b/test/lisp/calendar/diary-icalendar-resources/import-rrule-count-monthly.diary-all
new file mode 100644
index 00000000000..af145bbdee9
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-rrule-count-monthly.diary-all
@@ -0,0 +1,3 @@
1&%%(diary-rrule :rule '((FREQ MONTHLY) (INTERVAL 1) (COUNT 5)) :start
2 '(0 0 9 19 9 2003 5 -1 nil) :duration
3 '(0 30 2 0 nil nil nil -1 nil)) rrule count monthly
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-rrule-count-yearly.diary-all b/test/lisp/calendar/diary-icalendar-resources/import-rrule-count-yearly.diary-all
new file mode 100644
index 00000000000..5b78f608f12
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-rrule-count-yearly.diary-all
@@ -0,0 +1,3 @@
1&%%(diary-rrule :rule '((FREQ YEARLY) (INTERVAL 1) (COUNT 5)) :start
2 '(0 0 9 19 9 2003 5 -1 nil) :duration
3 '(0 30 2 0 nil nil nil -1 nil)) rrule count yearly
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-rrule-daily-two-day.diary-all b/test/lisp/calendar/diary-icalendar-resources/import-rrule-daily-two-day.diary-all
new file mode 100644
index 00000000000..9017c06900b
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-rrule-daily-two-day.diary-all
@@ -0,0 +1,3 @@
1&%%(diary-rrule :rule '((FREQ DAILY) (INTERVAL 2)) :start
2 '(0 0 9 19 9 2003 5 -1 nil) :duration
3 '(0 30 2 0 nil nil nil -1 nil)) rrule daily
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-rrule-daily-with-exceptions.diary-all b/test/lisp/calendar/diary-icalendar-resources/import-rrule-daily-with-exceptions.diary-all
new file mode 100644
index 00000000000..0407aa81b21
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-rrule-daily-with-exceptions.diary-all
@@ -0,0 +1,4 @@
1&%%(diary-rrule :rule '((FREQ DAILY) (INTERVAL 2)) :exclude
2 '((9 21 2003) (9 25 2003)) :start
3 '(0 0 9 19 9 2003 5 -1 nil) :duration
4 '(0 30 2 0 nil nil nil -1 nil)) rrule daily with exceptions
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-rrule-daily.diary-all b/test/lisp/calendar/diary-icalendar-resources/import-rrule-daily.diary-all
new file mode 100644
index 00000000000..993d3b9bb5e
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-rrule-daily.diary-all
@@ -0,0 +1,2 @@
1&%%(diary-rrule :rule '((FREQ DAILY)) :start '(0 0 9 19 9 2003 5 -1 nil)
2 :duration '(0 30 2 0 nil nil nil -1 nil)) rrule daily
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-rrule-monthly-no-end.diary-all b/test/lisp/calendar/diary-icalendar-resources/import-rrule-monthly-no-end.diary-all
new file mode 100644
index 00000000000..e69a6ec690c
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-rrule-monthly-no-end.diary-all
@@ -0,0 +1,3 @@
1&%%(diary-rrule :rule '((FREQ MONTHLY)) :start
2 '(0 0 9 19 9 2003 5 -1 nil) :duration
3 '(0 30 2 0 nil nil nil -1 nil)) rrule monthly no end
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-rrule-monthly-with-end.diary-all b/test/lisp/calendar/diary-icalendar-resources/import-rrule-monthly-with-end.diary-all
new file mode 100644
index 00000000000..a699498be77
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-rrule-monthly-with-end.diary-all
@@ -0,0 +1,3 @@
1&%%(diary-rrule :rule '((FREQ MONTHLY) (UNTIL (8 19 2005))) :start
2 '(0 0 9 19 9 2003 5 -1 nil) :duration
3 '(0 30 2 0 nil nil nil -1 nil)) rrule monthly with end
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-rrule-weekly.diary-all b/test/lisp/calendar/diary-icalendar-resources/import-rrule-weekly.diary-all
new file mode 100644
index 00000000000..89ba3e96149
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-rrule-weekly.diary-all
@@ -0,0 +1,2 @@
1&%%(diary-rrule :rule '((FREQ WEEKLY)) :start '(0 0 9 19 9 2003 5 -1 nil)
2 :duration '(0 30 2 0 nil nil nil -1 nil)) rrule weekly
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-rrule-yearly.diary-all b/test/lisp/calendar/diary-icalendar-resources/import-rrule-yearly.diary-all
new file mode 100644
index 00000000000..81220aac0cd
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-rrule-yearly.diary-all
@@ -0,0 +1,3 @@
1&%%(diary-rrule :rule '((FREQ YEARLY) (INTERVAL 2)) :start
2 '(0 0 9 19 9 2003 5 -1 nil) :duration
3 '(0 30 2 0 nil nil nil -1 nil)) rrule yearly
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-time-format-12hr-blank.diary-iso b/test/lisp/calendar/diary-icalendar-resources/import-time-format-12hr-blank.diary-iso
new file mode 100644
index 00000000000..0945cfc0b60
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-time-format-12hr-blank.diary-iso
@@ -0,0 +1 @@
&2003/9/19 9.00h-11.30h 12hr blank-padded
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-with-attachment.diary-iso b/test/lisp/calendar/diary-icalendar-resources/import-with-attachment.diary-iso
new file mode 100644
index 00000000000..b16022908d9
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-with-attachment.diary-iso
@@ -0,0 +1,3 @@
1&2003/9/19 09:00 Has an attachment
2 Attachment: R3Jl.plain
3 UID: f9fee9a0-1231-4984-9078-f1357db352db
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-with-timezone.diary-iso b/test/lisp/calendar/diary-icalendar-resources/import-with-timezone.diary-iso
new file mode 100644
index 00000000000..56f91066f73
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-with-timezone.diary-iso
@@ -0,0 +1,3 @@
1&2012/1/15 15:00-15:30 standardtime
2
3&2012/12/15 11:00-11:30 daylightsavingtime
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-with-uid.diary-american b/test/lisp/calendar/diary-icalendar-resources/import-with-uid.diary-american
new file mode 100644
index 00000000000..9b2f06afc26
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-with-uid.diary-american
@@ -0,0 +1,2 @@
1&9/19/2003 09:00-11:30 non-recurring
2 UID: 1234567890uid
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-with-uid.diary-european b/test/lisp/calendar/diary-icalendar-resources/import-with-uid.diary-european
new file mode 100644
index 00000000000..95db4d40151
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-with-uid.diary-european
@@ -0,0 +1,2 @@
1&19/9/2003 09:00-11:30 non-recurring
2 UID: 1234567890uid
diff --git a/test/lisp/calendar/diary-icalendar-resources/import-with-uid.diary-iso b/test/lisp/calendar/diary-icalendar-resources/import-with-uid.diary-iso
new file mode 100644
index 00000000000..d372e5a3d1f
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-resources/import-with-uid.diary-iso
@@ -0,0 +1,2 @@
1&2003/9/19 09:00-11:30 non-recurring
2 UID: 1234567890uid
diff --git a/test/lisp/calendar/diary-icalendar-tests.el b/test/lisp/calendar/diary-icalendar-tests.el
new file mode 100644
index 00000000000..b502dc72059
--- /dev/null
+++ b/test/lisp/calendar/diary-icalendar-tests.el
@@ -0,0 +1,1278 @@
1;;; diary-icalendar-tests.el --- Tests for diary-icalendar -*- lexical-binding: t; -*-
2;; Copyright (C) 2025 Free Software Foundation, Inc.
3
4;; This file is part of GNU Emacs.
5
6;; GNU Emacs is free software: you can redistribute it and/or modify
7;; it under the terms of the GNU General Public License as published by
8;; the Free Software Foundation, either version 3 of the License, or
9;; (at your option) any later version.
10
11;; GNU Emacs is distributed in the hope that it will be useful,
12;; but WITHOUT ANY WARRANTY; without even the implied warranty of
13;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14;; GNU General Public License for more details.
15
16;; You should have received a copy of the GNU General Public License
17;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
18
19;;; Code:
20
21(eval-when-compile (require 'cl-lib))
22(eval-when-compile (require 'icalendar-macs))
23(require 'diary-icalendar)
24(require 'icalendar-parser)
25(require 'icalendar-utils)
26(require 'icalendar)
27(require 'ert)
28(require 'ert-x)
29(require 'seq)
30
31
32;; Tests for diary import functions
33(defconst icalendar-resources-directory
34 (expand-file-name "test/lisp/calendar/icalendar-resources"
35 source-directory))
36
37(defconst diary-icalendar-resources-directory
38 (expand-file-name "test/lisp/calendar/diary-icalendar-resources"
39 source-directory))
40
41(defun dit:icalendar-resource-file (filename)
42 ;; Return a filename from the ./icalendar-resources directory:
43 (file-name-concat icalendar-resources-directory filename))
44
45(defun dit:resource-file (filename)
46 ;; Return a filename from the ./diary-icalendar-resources directory:
47 (file-name-concat diary-icalendar-resources-directory filename))
48
49(defun dit:file-contents (filename)
50 "Return literal contents of FILENAME."
51 (with-temp-buffer
52 (let ((coding-system-for-read 'raw-text)
53 (inhibit-eol-conversion t))
54 (insert-file-contents-literally filename)
55 (buffer-string))))
56
57(defmacro dit:with-tz (tz &rest body)
58 "Evaluate BODY with time zone TZ in effect."
59 `(let ((old-tz (getenv "TZ")))
60 (unwind-protect
61 (progn
62 (setenv "TZ" ,tz)
63 ,@body)
64 (setenv "TZ" old-tz))))
65
66(defun dit:import-file (ics-file)
67 "Test diary import of ICS-FILE.
68
69ICS-FILE names a .ics file in icalendar-resources directory. The
70calendar in ICS-FILE is parsed and imported in ISO, European, and
71American date styles. The output of each import is compared against the
72contents of any diary files with the same base name as ICS-FILE and
73extensions \".diary-all\", \".diary-american\", \".diary-european\", or
74\".diary-iso\"."
75 (let* ((basename (file-name-base ics-file))
76 (ics-file (dit:icalendar-resource-file ics-file))
77 (import-buffer (icalendar-unfolded-buffer-from-file ics-file))
78 (all-file (dit:resource-file (concat basename ".diary-all")))
79 (iso-file (dit:resource-file (concat basename ".diary-iso")))
80 (european-file (dit:resource-file (concat basename ".diary-european")))
81 (american-file (dit:resource-file (concat basename ".diary-american"))))
82 (with-current-buffer import-buffer
83 (when (file-exists-p all-file)
84 (calendar-set-date-style 'american) ; because it's the default
85 (dit:-do-test-import all-file))
86 (when (file-exists-p iso-file)
87 (calendar-set-date-style 'iso)
88 (dit:-do-test-import iso-file))
89 (when (file-exists-p european-file)
90 (calendar-set-date-style 'european)
91 (dit:-do-test-import european-file))
92 (when (file-exists-p american-file)
93 (calendar-set-date-style 'american)
94 (dit:-do-test-import american-file))
95 (set-buffer-modified-p nil)) ; so we can kill it without being asked
96 (kill-buffer import-buffer)))
97
98(defun dit:-do-test-import (diary-filename)
99 "Import iCalendar in current buffer and compare the result with DIARY-FILENAME."
100 (ert-with-temp-file temp-file
101 :suffix "icalendar-test-diary"
102 (dit:with-tz "Europe/Vienna"
103 ;; There's no way to make the test data independent of the system
104 ;; time zone unless diary gains time zone awareness/syntax, so we have
105 ;; to choose some time zone or other to standardize on for the import
106 ;; tests. "Europe/Vienna" is an arbitrary choice; it's simply the one
107 ;; I originally generated the test data files in.
108 ;; N.B. "Europe/Vienna" = "CET-1CEST,M3.5.0/02:00,M10.5.0/03:00"
109 (di:import-buffer temp-file t t))
110 (save-excursion
111 (find-file temp-file)
112 (let ((result (buffer-substring-no-properties (point-min) (point-max)))
113 (expected (dit:file-contents diary-filename)))
114 ;; Trim the result so that whitespace produced by the importer
115 ;; need not be committed in the test data files:
116 (should (equal (string-trim result)
117 (string-trim expected)))
118 ;; This is useful for debugging differences when tests are failing:
119 ;; (unless (equal (string-trim result)
120 ;; (string-trim expected))
121 ;; (let ((result-buf (current-buffer))
122 ;; (diary-buf (find-file diary-filename)))
123 ;; (ediff-buffers result-buf ; actual output
124 ;; diary-buf)
125 ;; (switch-to-buffer-other-frame "*Ediff Control Panel*")
126 ;; (error "Unexpected result; see ediff")))
127 ))
128 (kill-buffer (find-buffer-visiting temp-file))))
129
130(ert-deftest dit:import-non-recurring ()
131 "Import tests for standard, non-recurring events."
132 (dit:import-file "import-non-recurring-1.ics")
133 (dit:import-file "import-non-recurring-all-day.ics")
134 (dit:import-file "import-non-recurring-long-summary.ics")
135 (dit:import-file "import-non-recurring-block.ics")
136 (dit:import-file "import-non-recurring-folded-summary.ics")
137 (dit:import-file "import-non-recurring-another-example.ics"))
138
139(ert-deftest dit:import-w/legacy-vars ()
140 "Import tests using legacy import variables"
141 (let ((icalendar-import-format "%s%c%d%l%o%t%u%U")
142 (icalendar-import-format-summary "%s")
143 (icalendar-import-format-class "\n CLASS=%s")
144 (icalendar-import-format-description "\n DESCRIPTION=%s")
145 (icalendar-import-format-location "\n LOCATION=%s")
146 (icalendar-import-format-organizer "\n ORGANIZER=%s")
147 (icalendar-import-format-status "\n STATUS=%s")
148 (icalendar-import-format-url "\n URL=%s")
149 (icalendar-import-format-uid "\n UID=%s"))
150 (dit:import-file "import-legacy-vars.ics")))
151
152(defun dit:legacy-import-function (vevent)
153 "Example function value for `icalendar-import-format'"
154 (let ((props (nth 2 (car vevent))))
155 (mapconcat
156 (lambda (prop)
157 (format " %s: %s\n"
158 (symbol-name (nth 0 prop))
159 (nth 2 prop)))
160 props)))
161
162(ert-deftest dit:import-w/legacy-function ()
163 "Import tests using legacy import variables"
164 (let ((icalendar-import-format 'dit:legacy-import-function))
165 (dit:import-file "import-legacy-function.ics")))
166
167(ert-deftest dit:import-w/time-format ()
168 "Import tests for customized `diary-icalendar-time-format'"
169 (let ((diary-icalendar-time-format "%l.%Mh"))
170 (dit:import-file "import-time-format-12hr-blank.ics")))
171
172(ert-deftest dit:import-rrule ()
173 "Import tests for recurring events."
174 (dit:import-file "import-rrule-daily.ics")
175 (dit:import-file "import-rrule-daily-two-day.ics")
176 (dit:import-file "import-rrule-daily-with-exceptions.ics")
177 (dit:import-file "import-rrule-weekly.ics")
178 (dit:import-file "import-rrule-monthly-no-end.ics")
179 (dit:import-file "import-rrule-monthly-with-end.ics")
180 (dit:import-file "import-rrule-anniversary.ics")
181 (dit:import-file "import-rrule-yearly.ics")
182 (dit:import-file "import-rrule-count-bi-weekly.ics")
183 (dit:import-file "import-rrule-count-daily-short.ics")
184 (dit:import-file "import-rrule-count-daily-long.ics")
185 (dit:import-file "import-rrule-count-monthly.ics")
186 (dit:import-file "import-rrule-count-every-second-month.ics")
187 (dit:import-file "import-rrule-count-yearly.ics")
188 (dit:import-file "import-rrule-count-every-second-year.ics"))
189
190(ert-deftest dit:import-duration ()
191 (dit:import-file "import-duration.ics")
192 ;; duration-2: this is actually an rrule test
193 (dit:import-file "import-duration-2.ics"))
194
195(ert-deftest dit:import-multiple-vcalendars ()
196 (dit:import-file "import-multiple-vcalendars.ics"))
197
198(ert-deftest dit:import-with-uid ()
199 "Perform import test with uid."
200 (dit:import-file "import-with-uid.ics"))
201
202(ert-deftest dit:import-with-attachment ()
203 "Test importing an attached file to `icalendar-attachment-directory'"
204 (ert-with-temp-directory temp-dir
205 (let ((di:attachment-directory temp-dir)
206 (uid-dir (file-name-concat temp-dir
207 ;; Event's UID:
208 "f9fee9a0-1231-4984-9078-f1357db352db")))
209 (dit:import-file "import-with-attachment.ics")
210 (should (file-directory-p uid-dir))
211 (let ((files (directory-files uid-dir t
212 ;; First 4 chars of base64-string:
213 "R3Jl")))
214 (should (length= files 1))
215 (with-temp-buffer
216 (insert-file-contents (car files))
217 (should (equal "Greetings! I am a base64-encoded file"
218 (buffer-string))))))))
219
220(ert-deftest dit:import-with-timezone ()
221 (dit:import-file "import-with-timezone.ics"))
222
223(ert-deftest dit:import-real-world ()
224 "Import tests of other real world data"
225 ;; N.B. Not all data from these files is expected to be imported
226 ;; without any pre-parsing cleanup, since they are in some cases
227 ;; malformed. The test data matches what the importer should produce
228 ;; in its default configuration.
229 (dit:with-tz "Asia/Kolkata"
230 ;; Indian Standard Time, used in this file, does not adjust for
231 ;; daylight savings; so we use that time zone to keep this test
232 ;; from failing on systems in a time zone that does:
233 (dit:import-file "import-real-world-2003-05-29.ics"))
234 (dit:with-tz "Asia/Tehran"
235 ;; For the same reason, we use "Asia/Tehran" here:
236 (dit:import-file "import-real-world-no-dst.ics"))
237 (dit:import-file "import-real-world-2003-06-18a.ics")
238 ;; FIXME: this test seems to be failing due to an invisible unicode
239 ;; error of some sort. The import result and the expected output are
240 ;; visually identical and ediff shows no differences in the buffers,
241 ;; but the strings are apparently not `equal', and comparing them
242 ;; character-by-character shows that they somehow differ at the "ü" in
243 ;; "früher". But `describe-char' there shows no differences so far as
244 ;; I can see.
245 ;(dit:import-file "import-real-world-2003-06-18b.ics")
246 (dit:import-file "import-real-world-2004-11-19.ics")
247 (dit:import-file "import-real-world-2005-02-07.ics")
248 (dit:import-file "import-real-world-2005-03-01.ics"))
249
250(ert-deftest dit:import-bug-6766 ()
251 ;;bug#6766 -- multiple byday values in a weekly rrule
252 (dit:import-file "import-bug-6766.ics"))
253
254(ert-deftest dit:import-bug-11473 ()
255 ;; bug#11473 -- illegal tzid
256 (dit:import-file "import-bug-11473.ics"))
257
258(ert-deftest dit:import-bug-22092 ()
259 ;; bug#22092 -- mixed line endings
260 (let ((ical:pre-unfolding-hook '(ical:fix-line-endings)))
261 (dit:import-file "import-bug-22092.ics")))
262
263(ert-deftest dit:import-bug-24199 ()
264 ;;bug#24199 -- monthly rule with byday-clause
265 (dit:import-file "import-bug-24199.ics"))
266
267(ert-deftest dit:import-bug-33277 ()
268 ;;bug#33277 -- start time equals end time
269 (dit:import-file "import-bug-33277.ics"))
270
271
272
273
274;; Tests for diary export functions
275(cl-defmacro dit:parse-test (entry &key parser type number
276 bindings tests
277 source)
278 "Create a test which parses data from ENTRY.
279
280PARSER should be a zero-argument function which parses data of TYPE in a
281buffer containing ENTRY. The defined test passes if PARSER returns a
282list of NUMBER objects which satisfy TYPE. If NUMBER is nil, the return
283value of parser must be a single value satisfying TYPE.
284
285BINDINGS, if given, will be evaluated and made available in the lexical
286environment where PARSER is called; this can be used to temporarily set
287variables that affect parsing.
288
289TESTS, if given, is an additional test form that will be evaluated after
290the main tests. The variable `parsed' will be bound to the return value
291of PARSER when TESTS are evaluated.
292
293SOURCE, if given, should be a symbol; it is used to name the test."
294 (let ((parser-form `(funcall (function ,parser))))
295 `(ert-deftest
296 ,(intern (concat "diary-icalendar-test-"
297 (string-replace "diary-icalendar-" ""
298 (symbol-name parser))
299 (if source (concat "/" (symbol-name source)) "")))
300 ()
301 ,(format "Does `%s' correctly parse `%s' in diary entries?" parser type)
302 (let* ((parse-buf (get-buffer-create "*iCalendar Parse Test*"))
303 (unparsed ,entry))
304 (set-buffer parse-buf)
305 (erase-buffer)
306 (insert unparsed)
307 (goto-char (point-min))
308 (let* (,@bindings
309 (parsed ,parser-form))
310 (when ,number
311 (should (length= parsed ,number))
312 (should (seq-every-p (lambda (val) (cl-typep val ,type))
313 parsed)))
314 (unless ,number
315 (should (cl-typep parsed ,type)))
316 ,tests)))))
317
318(dit:parse-test
319 "2025-04-01 A basic entry
320 Other data"
321:parser di:parse-entry-type
322:type 'symbol
323:source vevent
324:tests (should (eq parsed 'ical:vevent)))
325
326(dit:parse-test
327 "&2025-04-01 A nonmarking journal entry
328 Other data"
329:parser di:parse-entry-type
330:bindings ((di:export-nonmarking-as-vjournal t))
331:type 'symbol
332:source vjournal
333:tests (should (eq parsed 'ical:vjournal)))
334
335(dit:parse-test
336 "2025-04-01 Due: some task
337 Other data"
338:parser di:parse-entry-type
339:bindings ((di:todo-regexp "Due: "))
340:type 'symbol
341:source vtodo
342:tests (should (eq parsed 'ical:vtodo)))
343
344(defun dit:parse-vevent-transparency ()
345 "Call `di:parse-transparency' with \\='icalendar-vevent"
346 (di:parse-transparency 'ical:vevent))
347
348(dit:parse-test
349 "&%%(diary-anniversary 7 28 1985) A transparent anniversary"
350 :parser dit:parse-vevent-transparency
351 :type 'ical:transp
352 :number 1
353 :source nonmarking
354 :tests
355 (ical:with-property (car parsed) nil
356 (should (equal value "TRANSPARENT"))))
357
358(dit:parse-test
359 "2025-04-01 Team Meeting
360 Some data
361 Organizer: Mr. Foo <foo@example.com>
362 Attendees: Baz Bar <baz@example.com>
363 Alice Unternehmer <alice@example.com> (some other data)
364 Other data"
365:parser di:parse-attendees-and-organizer
366:number 3
367:type '(or ical:attendee ical:organizer)
368:tests
369(dolist (p parsed)
370 (ical:with-property p
371 ((ical:cnparam :value name))
372 (cond ((equal value "mailto:foo@example.com")
373 (should (equal name "Mr. Foo"))
374 (should (ical:organizer-property-p p)))
375 ((equal value "mailto:baz@example.com")
376 (should (equal name "Baz Bar"))
377 (should (ical:attendee-property-p p)))
378 ((equal value "mailto:alice@example.com")
379 (should (equal name "Alice Unternehmer"))
380 (should (ical:attendee-property-p p)))
381 (t (error "Incorrectly parsed attendee address: %s" value))))))
382
383(dit:parse-test
384 "2025-04-01 An event with a UID
385 Some data
386 UID: emacs174560213714413195191
387 Other data"
388:parser di:parse-uid
389:bindings ((diary-date-forms diary-iso-date-forms))
390:type 'ical:uid
391:tests
392(ical:with-property (car parsed) nil
393 (should (equal "emacs174560213714413195191" value))))
394
395(dit:parse-test
396 "2025-04-01 An event with a different style of UID
397 Some data
398 UID: 197846d7-51be-4d8e-837f-7e132286e7cf
399 Other data"
400:parser di:parse-uid
401:source with-org-id-uuid
402:bindings ((diary-date-forms diary-iso-date-forms))
403:type 'ical:uid
404:tests
405(ical:with-property (car parsed) nil
406 (should (equal "197846d7-51be-4d8e-837f-7e132286e7cf" value))))
407
408(dit:parse-test
409 "2025-04-01 An event with a status
410 Some data
411 Status: confirmed
412 Other data"
413:parser di:parse-status
414:bindings ((diary-date-forms diary-iso-date-forms))
415:type 'ical:status
416:tests
417(ical:with-property (car parsed) nil
418 (should (equal "CONFIRMED" value))))
419
420(dit:parse-test
421 "2025-04-01 An event with an access classification
422 Some data
423 Class: private
424 Other data"
425:parser di:parse-class
426:source private
427:bindings ((diary-date-forms diary-iso-date-forms))
428:type 'ical:class
429:tests
430(ical:with-property (car parsed) nil
431 (should (equal "PRIVATE" value))))
432
433(dit:parse-test
434 "2025-04-01 An event with an access classification
435 Some data
436 Access: public
437 Other data"
438:parser di:parse-class
439:source public
440:bindings ((diary-date-forms diary-iso-date-forms))
441:type 'ical:class
442:tests
443(ical:with-property (car parsed) nil
444 (should (equal "PUBLIC" value))))
445
446(dit:parse-test
447 "2025-04-01 An event with a location
448 Some data
449 Location: Sesamstraße 13
450 Other data"
451:parser di:parse-location
452:bindings ((diary-date-forms diary-iso-date-forms))
453:type 'ical:location
454:tests
455(ical:with-property (car parsed) nil
456 (should (equal "Sesamstraße 13" value))))
457
458(dit:parse-test
459 "2025-04-01 An event with an URL
460 Some data
461 URL: http://example.com/foo/bar?q=baz
462 Other data"
463:parser di:parse-url
464:bindings ((diary-date-forms diary-iso-date-forms))
465:type 'ical:url
466:tests
467(ical:with-property (car parsed) nil
468 (should (equal "http://example.com/foo/bar?q=baz" value))))
469
470
471;; N.B. There is no date at the start of the entry in the following two
472;; tests because di:parse-summary-and-description assumes that the date
473;; parsing functions have already moved the start of the restriction
474;; beyond it.
475(dit:parse-test
476 "Event summary
477 Some data
478 Other data"
479:parser di:parse-summary-and-description
480:number 2
481:type '(or ical:summary ical:description)
482:bindings ((diary-date-forms diary-iso-date-forms))
483:tests
484(ical:with-property (car parsed) nil (should (equal "Event summary" value))))
485
486(dit:parse-test
487 "Some data
488 Summary: Event summary
489 Other data"
490:parser di:parse-summary-and-description
491:number 2
492:bindings ((di:summary-regexp "^[[:space:]]+Summary: \\(.*\\)$"))
493:type '(or ical:summary ical:description)
494:bindings ((diary-date-forms diary-iso-date-forms))
495:source with-summary-regexp
496:tests
497(ical:with-property (car parsed) nil (should (equal "Event summary" value))))
498
499(dit:parse-test
500 "2025/04/01 Some entry"
501 :parser di:parse-date-form
502 :type 'ical:date
503 :bindings ((diary-date-forms diary-iso-date-forms))
504 :source iso-date
505 :tests
506 (progn
507 (should (= 2025 (calendar-extract-year parsed)))
508 (should (= 4 (calendar-extract-month parsed)))
509 (should (= 1 (calendar-extract-day parsed)))))
510
511(dit:parse-test
512 "2025-04-01 Some entry"
513 :parser di:parse-date-form
514 :type 'ical:date
515 :bindings ((diary-date-forms diary-iso-date-forms))
516 :source iso-date-dashes
517 :tests
518 (progn
519 (should (= 2025 (calendar-extract-year parsed)))
520 (should (= 4 (calendar-extract-month parsed)))
521 (should (= 1 (calendar-extract-day parsed)))))
522
523(dit:parse-test
524 "1/4/2025 Some entry"
525 :parser di:parse-date-form
526 :type 'ical:date
527 :bindings ((diary-date-forms diary-european-date-forms))
528 :source european-date
529 :tests
530 (progn
531 (should (= 2025 (calendar-extract-year parsed)))
532 (should (= 4 (calendar-extract-month parsed)))
533 (should (= 1 (calendar-extract-day parsed)))))
534
535(dit:parse-test
536 "4/1/2025 Some entry"
537 :parser di:parse-date-form
538 :type 'ical:date
539 :bindings ((diary-date-forms diary-american-date-forms))
540 :source american-date
541 :tests
542 (progn
543 (should (= 2025 (calendar-extract-year parsed)))
544 (should (= 4 (calendar-extract-month parsed)))
545 (should (= 1 (calendar-extract-day parsed)))))
546
547(dit:parse-test
548 "4/1 April Fool's"
549 :parser di:parse-date-form
550 :type 'list
551 :bindings ((diary-date-forms diary-american-date-forms))
552 :source generic-year-american
553 :tests
554 (progn
555 (should (eq t (calendar-extract-year parsed)))
556 (should (= 4 (calendar-extract-month parsed)))
557 (should (= 1 (calendar-extract-day parsed)))))
558
559(dit:parse-test
560 "1/5 Tag der Arbeit"
561 :parser di:parse-date-form
562 :type 'list
563 :bindings ((diary-date-forms diary-european-date-forms))
564 :source generic-year-european
565 :tests
566 (progn
567 (should (eq t (calendar-extract-year parsed)))
568 (should (= 5 (calendar-extract-month parsed)))
569 (should (= 1 (calendar-extract-day parsed)))))
570
571(dit:parse-test
572 "1/*/2025 Rent due"
573 :parser di:parse-date-form
574 :type 'list
575 :bindings ((diary-date-forms diary-european-date-forms))
576 :source generic-month
577 :tests
578 (progn
579 (should (= 2025 (calendar-extract-year parsed)))
580 (should (eq t (calendar-extract-month parsed)))
581 (should (= 1 (calendar-extract-day parsed)))))
582
583(dit:parse-test
584 "*/2/2025 Every day in February: go running"
585 :parser di:parse-date-form
586 :type 'list
587 :bindings ((diary-date-forms diary-european-date-forms))
588 :source generic-day
589 :tests
590 (progn
591 (should (= 2025 (calendar-extract-year parsed)))
592 (should (= 2 (calendar-extract-month parsed)))
593 (should (eq t (calendar-extract-day parsed)))))
594
595(dit:parse-test
596 "Friday
597 Lab meeting
598 Backup data"
599 :parser di:parse-weekday-name
600 :type 'integer
601 :tests
602 (should (= 5 parsed)))
603
604;;; Examples from the Emacs manual:
605(dit:parse-test
606 "12/22/2015 Twentieth wedding anniversary!"
607 :parser di:parse-date-form
608 :type 'ical:date
609 :bindings ((diary-date-forms diary-american-date-forms))
610 :source emacs-manual-sec33.10.1/1
611 :tests
612 (progn
613 (should (= 2015 (calendar-extract-year parsed)))
614 (should (= 12 (calendar-extract-month parsed)))
615 (should (= 22 (calendar-extract-day parsed)))))
616
617(dit:parse-test
618 ;; Generic date via unspecified year:
619 "10/22 Ruth's birthday."
620 :parser di:parse-date-form
621 :type 'list
622 :bindings ((diary-date-forms diary-american-date-forms))
623 :source emacs-manual-sec33.10.1/2
624 :tests
625 (progn
626 (should (eq t (calendar-extract-year parsed)))
627 (should (= 10 (calendar-extract-month parsed)))
628 (should (= 22 (calendar-extract-day parsed)))))
629
630(dit:parse-test
631 ;; Generic date via unspecified year:
632 "4/30 Results for April are due"
633 :parser di:parse-date-form
634 :type 'list
635 :bindings ((diary-date-forms diary-american-date-forms))
636 :source emacs-manual-sec33.10.3/3
637 :tests
638 (progn
639 (should (eq t (calendar-extract-year parsed)))
640 (should (= 4 (calendar-extract-month parsed)))
641 (should (= 30 (calendar-extract-day parsed)))))
642
643(dit:parse-test
644 ;; Generic date with asterisks:
645 "* 21, *: Payday"
646 :parser di:parse-date-form
647 :type 'list
648 :bindings ((diary-date-forms diary-american-date-forms))
649 :source emacs-manual-sec33.10.1/3
650 :tests
651 (progn
652 (should (eq t (calendar-extract-year parsed)))
653 (should (eq t (calendar-extract-month parsed)))
654 (should (= 21 (calendar-extract-day parsed)))))
655
656(dit:parse-test
657 ;; Generic date with asterisks:
658 "*/25 Monthly cycle finishes"
659 :parser di:parse-date-form
660 :type 'list
661 :bindings ((diary-date-forms diary-american-date-forms))
662 :source emacs-manual-sec33.10.3/4
663 :tests
664 (progn
665 (should (eq t (calendar-extract-year parsed)))
666 (should (eq t (calendar-extract-month parsed)))
667 (should (= 25 (calendar-extract-day parsed)))))
668
669(dit:parse-test
670 ;; Weekday name:
671 "Tuesday--weekly meeting with grad students at 10am
672 Supowit, Shen, Bitner, and Kapoor to attend."
673 :parser di:parse-weekday-name
674 :type 'integer
675 :source emacs-manual-sec33.10.1/4
676 :tests
677 (should (= 2 parsed)))
678
679(dit:parse-test
680 ;; Weekday name:
681 "Friday Don't leave without backing up files"
682 :parser di:parse-weekday-name
683 :type 'integer
684 :source emacs-manual-sec33.10.3/5
685 :tests
686 (should (= 5 parsed)))
687
688(dit:parse-test
689 ;; Date with two-digit year:
690 "1/13/89 Friday the thirteenth!!"
691 :parser di:parse-date-form
692 :type 'list
693 :bindings ((diary-date-forms diary-american-date-forms))
694 :source emacs-manual-sec33.10.1/5
695 :tests
696 (progn
697 (should (= 1989 (calendar-extract-year parsed)))
698 (should (= 1 (calendar-extract-month parsed)))
699 (should (= 13 (calendar-extract-day parsed)))))
700
701(dit:parse-test
702 ;; Date with two-digit year:
703 "4/20/12 Switch-over to new tabulation system"
704 :parser di:parse-date-form
705 :type 'list
706 :bindings ((diary-date-forms diary-american-date-forms))
707 :source emacs-manual-sec33.10.3/1
708 :tests
709 (progn
710 (should (= 2012 (calendar-extract-year parsed)))
711 (should (= 4 (calendar-extract-month parsed)))
712 (should (= 20 (calendar-extract-day parsed)))))
713
714(dit:parse-test
715 ;; Abbreviated weekday name:
716 "thu 4pm squash game with Lloyd."
717 :parser di:parse-weekday-name
718 :type 'integer
719 :bindings ((diary-date-forms diary-american-date-forms))
720 :source emacs-manual-sec33.10.1/6
721 :tests
722 (should (= 4 parsed)))
723
724(dit:parse-test
725 ;; Abbreviated month name:
726 "mar 16 Dad's birthday"
727 :parser di:parse-date-form
728 :type 'list
729 :bindings ((diary-date-forms diary-american-date-forms))
730 :source emacs-manual-sec33.10.1/7
731 :tests
732 (progn
733 (should (eq t (calendar-extract-year parsed)))
734 (should (= 3 (calendar-extract-month parsed)))
735 (should (= 16 (calendar-extract-day parsed)))))
736
737(dit:parse-test
738 ;; Abbreviated month name with following period:
739 "apr. 25 Start tabulating annual results"
740 :parser di:parse-date-form
741 :type 'list
742 :bindings ((diary-date-forms diary-american-date-forms))
743 :source emacs-manual-sec33.10.3/2
744 :tests
745 (progn
746 (should (eq t (calendar-extract-year parsed)))
747 (should (= 4 (calendar-extract-month parsed)))
748 (should (= 25 (calendar-extract-day parsed)))))
749
750(dit:parse-test
751 ;; Long form date:
752 "April 15, 2016 Income tax due."
753 :parser di:parse-date-form
754 :type 'ical:date
755 :bindings ((diary-date-forms diary-american-date-forms))
756 :source emacs-manual-sec33.10.1/8
757 :tests
758 (progn
759 (should (= 2016 (calendar-extract-year parsed)))
760 (should (= 4 (calendar-extract-month parsed)))
761 (should (= 15 (calendar-extract-day parsed)))))
762
763(dit:parse-test
764 ;; Generic monthly date:
765 "* 15 time cards due."
766 :parser di:parse-date-form
767 :type 'list
768 :bindings ((diary-date-forms diary-american-date-forms))
769 :source emacs-manual-sec33.10.1/9
770 :tests
771 (progn
772 (should (eq t (calendar-extract-year parsed)))
773 (should (eq t (calendar-extract-month parsed)))
774 (should (= 15 (calendar-extract-day parsed)))))
775
776(dit:parse-test
777 "%%(diary-anniversary 5 28 1995) A birthday"
778 :parser di:parse-sexp
779 :type 'list
780 :tests (should (eq 'diary-anniversary (car parsed))))
781
782(dit:parse-test
783 "%%(diary-time-block :start (0 0 13 2 4 2025 6 t 7200)
784 :end (0 0 11 4 4 2025 6 t 7200))
785 A multiday event with different start and end times"
786 :parser di:parse-sexp
787 :type 'list
788 :source multiline-sexp
789 :tests (should (eq 'diary-time-block (car parsed))))
790
791(defun dit:entry-parser ()
792 "Call `di:parse-entry' on the full test buffer"
793 (let ((tz
794 (cond
795 ((eq 'local di:time-zone-export-strategy)
796 (di:current-tz-to-vtimezone))
797 ((listp di:time-zone-export-strategy)
798 (di:current-tz-to-vtimezone di:time-zone-export-strategy)))))
799
800 (di:parse-entry (point-min) (point-max) tz)))
801
802(dit:parse-test
803 ;; Weekly event, abbreviated weekday name:
804 "thu 4pm squash game with Lloyd."
805 :parser dit:entry-parser
806 :type 'ical:vevent
807 :number 1
808 :bindings ((diary-date-forms diary-american-date-forms))
809 :source emacs-manual-sec33.10.1/6
810 :tests
811 (ical:with-component (car parsed)
812 ((ical:dtstart :value dtstart)
813 (ical:rrule :value rrule)
814 (ical:summary :value summary))
815 (should (equal summary "squash game with Lloyd."))
816 (should (equal (ical:date-time-to-date dtstart)
817 (calendar-nth-named-day 1 4 1 di:recurring-start-year)))
818 (should (= 16 (decoded-time-hour dtstart)))
819 (should (eq (ical:recur-freq rrule) 'WEEKLY))
820 (should (equal (ical:recur-by* 'BYDAY rrule) (list 4)))))
821
822(dit:parse-test
823 ;; Multiline entry, parsed as one event:
824 "2025-05-03
825 9AM Lab meeting
826 Gunther to present on new assay
827 12:30-1:30PM Lunch with Phil
828 16:00 Experiment A finishes; move to freezer"
829 :parser dit:entry-parser
830 :source multiline-single
831 :type 'ical:vevent
832 :number 1
833 :bindings ((diary-date-forms diary-iso-date-forms)))
834
835(dit:parse-test
836 ;; Multiline entry, parsed linewise as three events:
837 "2025-05-03
838 9AM Lab meeting
839 Gunther to present on new assay
840 12:30-1:30PM Lunch with Phil
841 16:00 Experiment A finishes; move to freezer"
842 :parser dit:entry-parser
843 :source multiline-linewise
844 :type 'ical:vevent
845 :number 3
846 :bindings ((diary-date-forms diary-iso-date-forms)
847 (diary-icalendar-export-linewise t))
848 :tests
849 (progn
850 (dolist (event parsed)
851 (ical:with-component event
852 ((ical:dtstart :value-type start-type :value dtstart)
853 (ical:dtend :value-type end-type :value dtend)
854 (ical:summary :value summary))
855 (should (eq start-type 'ical:date-time))
856 (should (= 2025 (decoded-time-year dtstart)))
857 (should (= 5 (decoded-time-month dtstart)))
858 (should (= 3 (decoded-time-day dtstart)))
859 (when dtend
860 (should (eq end-type 'ical:date-time))
861 (should (= 2025 (decoded-time-year dtend)))
862 (should (= 5 (decoded-time-month dtend)))
863 (should (= 3 (decoded-time-day dtend))))
864 (cond ((equal summary "Lab meeting")
865 (should (= 9 (decoded-time-hour dtstart))))
866 ((equal summary "Lunch with Phil")
867 (should (= 12 (decoded-time-hour dtstart)))
868 (should (= 30 (decoded-time-minute dtstart)))
869 (should (= 13 (decoded-time-hour dtend)))
870 (should (= 30 (decoded-time-minute dtend))))
871 ((equal summary "Experiment A finishes; move to freezer")
872 (should (= 16 (decoded-time-hour dtstart))))
873 (t (error "Unknown event: %s" summary)))))))
874
875(dit:parse-test
876 ;; Multiline entry from the manual, parsed linewise:
877 ;; TODO: I've left the times verbatim in the example
878 ;; and in the tests, even though "2:30", "5:30" and "8:00"
879 ;; would most naturally be understood as PM times.
880 ;; Should probably fix the manual, then revise here.
881 "02/11/2012
882 Bill B. visits Princeton today
883 2pm Cognitive Studies Committee meeting
884 2:30-5:30 Liz at Lawrenceville
885 4:00pm Dentist appt
886 7:30pm Dinner at George's
887 8:00-10:00pm concert"
888 :parser dit:entry-parser
889 :type 'ical:vevent
890 :number 6
891 :bindings ((diary-date-forms diary-american-date-forms)
892 (diary-icalendar-export-linewise t))
893 :source emacs-manual-sec33.10.1/10
894 :tests
895 (progn
896 (dolist (event parsed)
897 (ical:with-component event
898 ((ical:dtstart :value-type start-type :value dtstart)
899 (ical:dtend :value-type end-type :value dtend)
900 (ical:summary :value summary))
901 (when (eq start-type 'ical:date)
902 (should (= 2012 (calendar-extract-year dtstart)))
903 (should (= 2 (calendar-extract-month dtstart)))
904 (should (= 11 (calendar-extract-day dtstart))))
905 (when (eq start-type 'ical:date-time)
906 (should (= 2012 (decoded-time-year dtstart)))
907 (should (= 2 (decoded-time-month dtstart)))
908 (should (= 11 (decoded-time-day dtstart))))
909 (when dtend
910 (should (eq end-type 'ical:date-time))
911 (should (= 2012 (decoded-time-year dtend)))
912 (should (= 2 (decoded-time-month dtend)))
913 (should (= 11 (decoded-time-day dtend))))
914 (cond ((equal summary "Bill B. visits Princeton today")
915 (should (eq start-type 'ical:date)))
916 ((equal summary "Cognitive Studies Committee meeting")
917 (should (= 14 (decoded-time-hour dtstart)))
918 (should (= 0 (decoded-time-minute dtstart))))
919 ((equal summary "Liz at Lawrenceville")
920 (should (= 2 (decoded-time-hour dtstart)))
921 (should (= 30 (decoded-time-minute dtstart)))
922 (should (= 5 (decoded-time-hour dtend)))
923 (should (= 30 (decoded-time-minute dtend))))
924 ((equal summary "Dentist appt")
925 (should (= 16 (decoded-time-hour dtstart)))
926 (should (= 0 (decoded-time-minute dtstart))))
927 ((equal summary "Dinner at George's")
928 (should (= 19 (decoded-time-hour dtstart)))
929 (should (= 30 (decoded-time-minute dtstart))))
930 ((equal summary "concert")
931 (should (= 8 (decoded-time-hour dtstart)))
932 (should (= 0 (decoded-time-minute dtstart)))
933 (should (= 22 (decoded-time-hour dtend)))
934 (should (= 0 (decoded-time-minute dtend))))
935 (t (error "Unknown event: %s" summary)))))))
936
937(dit:parse-test
938 ;; Same as the last, but with ignored data on the same line as the date
939 "02/11/2012 Ignored
940 2pm Cognitive Studies Committee meeting
941 2:30-5:30 Liz at Lawrenceville
942 4:00pm Dentist appt
943 7:30pm Dinner at George's
944 8:00-10:00pm concert"
945 :parser dit:entry-parser
946 :type 'ical:vevent
947 :number 5
948 :bindings ((diary-date-forms diary-american-date-forms)
949 (diary-icalendar-export-linewise t))
950 :source emacs-manual-sec33.10.1/10-first-line)
951
952(dit:parse-test
953 "%%(diary-anniversary 5 28 1995) H's birthday"
954 :parser dit:entry-parser
955 :type 'ical:vevent
956 :number 1
957 :bindings ((diary-date-forms diary-american-date-forms)
958 (calendar-date-style 'american))
959 :source diary-anniversary-recurrence
960 :tests
961 (ical:with-component (car parsed)
962 ((ical:dtstart :value dtstart)
963 (ical:rrule :value recur-value)
964 (ical:summary :value summary))
965 (should (equal dtstart '(5 28 1995)))
966 (should (eq (ical:recur-freq recur-value) 'YEARLY))
967 (should (equal summary "H's birthday"))))
968
969(dit:parse-test
970 "%%(diary-block 6 24 2012 7 10 2012) Vacation"
971 :parser dit:entry-parser
972 :type 'ical:vevent
973 :number 1
974 :bindings ((diary-date-forms diary-american-date-forms))
975 :source diary-block-recurrence
976 :tests
977 (ical:with-component (car parsed)
978 ((ical:dtstart :value dtstart)
979 (ical:rrule :value recur-value)
980 (ical:summary :value summary))
981 (should (equal dtstart '(6 24 2012)))
982 (should (equal (ical:recur-freq recur-value) 'DAILY))
983 (should (equal (ical:recur-until recur-value) '(7 10 2012)))
984 (should (equal summary "Vacation"))))
985
986(dit:parse-test
987 "%%(diary-cyclic 50 3 1 2012) Renew medication"
988 :parser dit:entry-parser
989 :type 'ical:vevent
990 :number 1
991 :bindings ((diary-date-forms diary-american-date-forms))
992 :source diary-cyclic-recurrence
993 :tests
994 (ical:with-component (car parsed)
995 ((ical:dtstart :value dtstart)
996 (ical:rrule :value recur-value)
997 (ical:summary :value summary))
998 (should (equal dtstart '(3 1 2012)))
999 (should (eq (ical:recur-freq recur-value) 'DAILY))
1000 (should (eq (ical:recur-interval-size recur-value) 50))
1001 (should (equal summary "Renew medication"))))
1002
1003(dit:parse-test
1004 "%%(diary-float 11 4 4) American Thanksgiving"
1005 :parser dit:entry-parser
1006 :type 'ical:vevent
1007 :number 1
1008 :bindings ((diary-date-forms diary-american-date-forms))
1009 :source diary-float-recurrence
1010 :tests
1011 (ical:with-component (car parsed)
1012 ((ical:dtstart :value dtstart)
1013 (ical:rrule :value recur-value)
1014 (ical:summary :value summary))
1015 (should (equal dtstart
1016 (calendar-nth-named-day 4 4 11 di:recurring-start-year)))
1017 (should (eq (ical:recur-freq recur-value) 'MONTHLY))
1018 (should (equal (ical:recur-by* 'BYMONTH recur-value) (list 11)))
1019 (should (equal (ical:recur-by* 'BYDAY recur-value) (list '(4 . 4))))
1020 (should (equal summary "American Thanksgiving"))))
1021
1022(dit:parse-test
1023 "%%(diary-offset '(diary-float t 3 4) 2) Monthly committee meeting"
1024 :parser dit:entry-parser
1025 :type 'ical:vevent
1026 :number 1
1027 :bindings ((diary-date-forms diary-american-date-forms))
1028 :source diary-offset-recurrence
1029 :tests
1030 (ical:with-component (car parsed)
1031 ((ical:dtstart :value dtstart)
1032 (ical:rrule :value recur-value)
1033 (ical:summary :value summary))
1034 (should (equal dtstart
1035 (calendar-nth-named-day 4 5 1 di:recurring-start-year)))
1036 (should (eq (ical:recur-freq recur-value) 'MONTHLY))
1037 ;; day 3 is Wednesday, so offset of 2 means Friday (=5):
1038 (should (equal (ical:recur-by* 'BYDAY recur-value) (list '(5 . 4))))
1039 (should (equal summary "Monthly committee meeting"))))
1040
1041(dit:parse-test
1042 "%%(diary-rrule :start '(11 11 2024)
1043 :rule '((FREQ WEEKLY))
1044 :exclude '((12 23 2024) (12 30 2024))
1045 ) Reading group"
1046 :parser dit:entry-parser
1047 :type 'ical:vevent
1048 :number 1
1049 :bindings ((diary-date-forms diary-american-date-forms))
1050 :source diary-rrule-recurrence
1051 :tests
1052 (ical:with-component (car parsed)
1053 ((ical:dtstart :value dtstart)
1054 (ical:rrule :value recur-value)
1055 (ical:exdate :values exdates)
1056 (ical:summary :value summary))
1057 (should (equal dtstart '(11 11 2024)))
1058 (should (eq (ical:recur-freq recur-value) 'WEEKLY))
1059 (should (equal exdates '((12 23 2024) (12 30 2024))))
1060 (should (equal summary "Reading group"))))
1061
1062(dit:parse-test
1063 "%%(diary-date '(10 11 12) 22 t) Rake leaves"
1064 :parser dit:entry-parser
1065 :type 'ical:vevent
1066 :number 1
1067 :bindings ((diary-date-forms diary-american-date-forms))
1068 :source diary-date-recurrence
1069 :tests
1070 (ical:with-component (car parsed)
1071 ((ical:dtstart :value dtstart)
1072 (ical:rrule :value recur-value)
1073 (ical:summary :value summary))
1074 (should (equal dtstart (list 10 22 di:recurring-start-year)))
1075 (should (eq (ical:recur-freq recur-value) 'YEARLY))
1076 (should (equal (ical:recur-by* 'BYMONTH recur-value) (list 10 11 12)))
1077 (should (equal (ical:recur-by* 'BYMONTHDAY recur-value) (list 22)))
1078 (should (equal summary "Rake leaves"))))
1079
1080(dit:parse-test
1081 ;; From the manual: "Suppose you get paid on the 21st of the month if
1082 ;; it is a weekday, and on the Friday before if the 21st is on a
1083 ;; weekend..."
1084 "%%(let ((dayname (calendar-day-of-week date))
1085 (day (cadr date)))
1086 (or (and (= day 21) (memq dayname '(1 2 3 4 5)))
1087 (and (memq day '(19 20)) (= dayname 5)))
1088 ) Pay check deposited"
1089 :parser dit:entry-parser
1090 :type 'ical:vevent
1091 :number 1
1092 :bindings ((diary-date-forms diary-american-date-forms)
1093 (di:export-sexp-enumeration-days 366))
1094 :source emacs-manual-sec33.13.10.7
1095 :tests
1096 (ical:with-component (car parsed)
1097 ((ical:dtstart :value dtstart)
1098 (ical:rdate :values rdates)
1099 (ical:summary :value summary))
1100 (should (equal summary "Pay check deposited"))
1101 (mapc
1102 (lambda (date)
1103 (should (or (and (= 21 (calendar-extract-day date))
1104 (memq (calendar-day-of-week date) (list 1 2 3 4 5)))
1105 (and (memq (calendar-extract-day date) (list 19 20))
1106 (= 5 (calendar-day-of-week date))))))
1107 (cons dtstart rdates))))
1108
1109(dit:parse-test
1110 "02/11/2012 4:00pm Exported with 'local strategy"
1111 :parser dit:entry-parser
1112 :type 'ical:vevent
1113 :number 1
1114 :bindings ((tz (getenv "TZ"))
1115 ;; Refresh output from `calendar-current-time-zone':
1116 (calendar-current-time-zone-cache nil)
1117 ;; Assume Eastern European Time (UTC+2, UTC+3 daylight saving)
1118 (_ (setenv "TZ" "EET-2EEST,M3.5.0/3,M10.5.0/4"))
1119 ;; ...and use this TZ when exporting:
1120 (diary-icalendar-time-zone-export-strategy 'local)
1121 (diary-date-forms diary-european-date-forms))
1122 :source tz-strategy-local
1123 :tests
1124 (unwind-protect
1125 (let ((vtimezone (di:current-tz-to-vtimezone)))
1126 (ical:with-component vtimezone
1127 ((ical:standard :first std)
1128 (ical:daylight :first dst))
1129 (should (= (* 2 60 60) (ical:with-property-of std 'ical:tzoffsetto)))
1130 (should (= (* 3 60 60) (ical:with-property-of dst 'ical:tzoffsetto))))
1131 (ical:with-component (car parsed)
1132 ((ical:dtstart :first start-node :value start))
1133 (should (= (* 2 60 60) (decoded-time-zone start)))
1134 (should (= 16 (decoded-time-hour start)))
1135 (should (ical:with-param-of start-node 'ical:tzidparam))))
1136 ;; restore time zone
1137 (setenv "TZ" tz)))
1138
1139(dit:parse-test
1140 "02/11/2012 4:00pm Exported with 'to-utc strategy"
1141 :parser dit:entry-parser
1142 :type 'ical:vevent
1143 :number 1
1144 :bindings ((tz (getenv "TZ"))
1145 ;; Assume Eastern European Time (UTC+2, UTC+3 daylight saving)
1146 (_ (setenv "TZ" "EET-2EEST,M3.5.0/3,M10.5.0/4"))
1147 ;; ...and convert times to UTC on export:
1148 (diary-icalendar-time-zone-export-strategy 'to-utc)
1149 (diary-date-forms diary-european-date-forms))
1150 :source tz-strategy-to-utc
1151 :tests
1152 (unwind-protect
1153 (ical:with-component (car parsed)
1154 ((ical:dtstart :first start-node :value start))
1155 (should (= 0 (decoded-time-zone start)))
1156 (should (= (- 16 2) (decoded-time-hour start)))
1157 (should-not (ical:with-param-of start-node 'ical:tzidparam)))
1158 ;; restore time zone
1159 (setenv "TZ" tz)))
1160
1161(dit:parse-test
1162 "02/11/2012 4:00pm Exported with 'floating strategy"
1163 :parser dit:entry-parser
1164 :type 'ical:vevent
1165 :number 1
1166 :bindings ((tz (getenv "TZ"))
1167 ;; Assume Eastern European Time (UTC+2, UTC+3 daylight saving)
1168 (_ (setenv "TZ" "EET-2EEST,M3.5.0/3,M10.5.0/4"))
1169 ;; ...but use floating times:
1170 (diary-icalendar-time-zone-export-strategy 'floating)
1171 (diary-date-forms diary-european-date-forms))
1172 :source tz-strategy-floating
1173 :tests
1174 (unwind-protect
1175 (ical:with-component (car parsed)
1176 ((ical:dtstart :first start-node :value start))
1177 (should (null (decoded-time-zone start)))
1178 (should (= 16 (decoded-time-hour start)))
1179 (should-not (ical:with-param-of start-node 'ical:tzidparam)))
1180
1181 ;; restore time zone
1182 (setenv "TZ" tz)))
1183
1184(dit:parse-test
1185 "02/11/2012 4:00pm Exported with tz info list"
1186 :parser dit:entry-parser
1187 :type 'ical:vevent
1188 :number 1
1189 :bindings (;; Encode Eastern European Time (UTC+2, UTC+3 daylight saving)
1190 ;; directly in the variable:
1191 (diary-icalendar-time-zone-export-strategy
1192 '(120 60 "EET" "EEST"
1193 (calendar-nth-named-day -1 0 3 year) ; last Sunday of March
1194 (calendar-nth-named-day -1 0 10 year) ; last Sunday of October
1195 240 180))
1196 (diary-date-forms diary-european-date-forms))
1197 :source tz-strategy-sexp
1198 :tests
1199 (let ((vtimezone (di:current-tz-to-vtimezone
1200 diary-icalendar-time-zone-export-strategy
1201 "EET")))
1202 (ical:with-component vtimezone
1203 ((ical:standard :first std)
1204 (ical:daylight :first dst))
1205 (should (= (* 2 60 60) (ical:with-property-of std 'ical:tzoffsetto)))
1206 (should (= (* 3 60 60) (ical:with-property-of dst 'ical:tzoffsetto))))
1207 (ical:with-component (car parsed)
1208 ((ical:dtstart :first start-node :value start))
1209 (should (= 7200 (decoded-time-zone start)))
1210 (should (= 16 (decoded-time-hour start)))
1211 (should (ical:with-param-of start-node 'ical:tzidparam)))))
1212
1213(defun dit:parse-@-location (type properties)
1214 "Example user function for parsing additional properties.
1215Parses anything following \"@\" to end of line as the entry's LOCATION."
1216 (ignore type properties)
1217 (goto-char (point-min))
1218 (when (re-search-forward "@\\([^\n]+\\)" nil t)
1219 (list (ical:make-property ical:location
1220 (string-trim (match-string 1))))))
1221
1222(dit:parse-test
1223 "2025/08/02 BBQ @ John's"
1224 :parser dit:entry-parser
1225 :type 'ical:vevent
1226 :number 1
1227 :bindings ((diary-icalendar-other-properties-parser #'dit:parse-@-location)
1228 (diary-date-forms diary-iso-date-forms))
1229 :source other-properties-parser
1230 :tests
1231 (ical:with-component (car parsed)
1232 ((ical:location :value location))
1233 (should (equal location "John's"))))
1234
1235(dit:parse-test
1236 "2025/05/15 11AM Department meeting
1237 Attendee: <mydept@example.com>"
1238 :parser dit:entry-parser
1239 :type 'ical:vevent
1240 :number 1
1241 :bindings ((diary-icalendar-export-alarms
1242 '((audio 10)
1243 (display 20 "In %t minutes: %s")
1244 (email 60 "In %t minutes: %s" ("myemail@example.com" from-entry))))
1245 (diary-date-forms diary-iso-date-forms))
1246 :source alarms-export
1247 :tests
1248 (ical:with-component (car parsed)
1249 ((ical:valarm :all valarms))
1250 (should (length= valarms 3))
1251 (dolist (valarm valarms)
1252 (ical:with-component valarm
1253 ((ical:action :value action)
1254 (ical:trigger :value trigger)
1255 (ical:summary :value summary)
1256 (ical:attendee :all attendee-nodes))
1257 (cond ((equal action "AUDIO")
1258 (should (eql -10 (decoded-time-minute trigger))))
1259 ((equal action "DISPLAY")
1260 (should (eql -20 (decoded-time-minute trigger)))
1261 (should (equal summary "In 20 minutes: Department meeting")))
1262 ((equal action "EMAIL")
1263 (should (eql -60 (decoded-time-minute trigger)))
1264 (should (equal summary "In 60 minutes: Department meeting"))
1265 (should (length= attendee-nodes 2))
1266 (let ((addrs (mapcar (lambda (n) (ical:with-node-value n))
1267 attendee-nodes)))
1268 (should (member "mailto:myemail@example.com" addrs))
1269 (should (member "mailto:mydept@example.com" addrs))))
1270 (t (error "Unknown alarm action %s" action)))))))
1271
1272
1273
1274;; Local Variables:
1275;; read-symbol-shorthands: (("dit:" . "diary-icalendar-test-") ("di:" . "diary-icalendar-") ("ical:" . "icalendar-"))
1276;; byte-compile-warnings: (not obsolete)
1277;; End:
1278;;; diary-icalendar-tests.el ends here
diff --git a/test/lisp/calendar/icalendar-ast-tests.el b/test/lisp/calendar/icalendar-ast-tests.el
new file mode 100644
index 00000000000..b4107b139a5
--- /dev/null
+++ b/test/lisp/calendar/icalendar-ast-tests.el
@@ -0,0 +1,112 @@
1;;; tests/icalendar-ast.el --- Tests for icalendar-ast -*- lexical-binding: t; -*-
2;; Copyright (C) 2025 Free Software Foundation, Inc.
3
4;; This file is part of GNU Emacs.
5
6;; GNU Emacs is free software: you can redistribute it and/or modify
7;; it under the terms of the GNU General Public License as published by
8;; the Free Software Foundation, either version 3 of the License, or
9;; (at your option) any later version.
10
11;; GNU Emacs is distributed in the hope that it will be useful,
12;; but WITHOUT ANY WARRANTY; without even the implied warranty of
13;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14;; GNU General Public License for more details.
15
16;; You should have received a copy of the GNU General Public License
17;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
18
19;;; Code:
20(require 'icalendar-ast)
21(require 'icalendar-parser)
22(require 'cl-lib)
23(eval-when-compile (require 'icalendar-macs))
24
25
26;; Tests for the high-level construction macros:
27(ert-deftest iat:make-param/nonlist ()
28 "Test that `icalendar-make-param' works as documented with a single value."
29 (let ((cnparam-node (ical:make-param ical:cnparam "John Doe")))
30 (should (ical:param-node-p cnparam-node))
31 (should (eq 'ical:cnparam (ical:ast-node-type cnparam-node)))
32 (ical:with-param cnparam-node
33 (should (cl-typep value 'ical:text))
34 (should (equal value "John Doe")))))
35
36(ert-deftest iat:make-param/list ()
37 "Test that `icalendar-make-param' works as documented with a list of values."
38 (let ((deltoparam-node (ical:make-param ical:deltoparam
39 (list "mailto:minionA@example.com"
40 "mailto:minionB@example.com"))))
41 (should (ical:param-node-p deltoparam-node))
42 (should (eq 'ical:deltoparam (ical:ast-node-type deltoparam-node)))
43 (ical:with-param deltoparam-node
44 (should (and (listp value-nodes) (length= value-nodes 2)))
45 (should (seq-every-p
46 (lambda (n) (eq 'ical:cal-address (ical:ast-node-type n)))
47 value-nodes))
48 (should (equal "mailto:minionA@example.com" (car values)))
49 (should (equal "mailto:minionB@example.com" (cadr values))))))
50
51(ert-deftest iat:make-property/nonlist ()
52 "Test that `icalendar-make-property' works as documented with a single value."
53 (let ((attendee-node
54 (ical:make-property ical:attendee "mailto:hermes@planetexpress.com"
55 (ical:cnparam "H. Conrad"))))
56 (should (ical:property-node-p attendee-node))
57 (should (eq 'ical:attendee (ical:ast-node-type attendee-node)))
58 (ical:with-property attendee-node
59 ((ical:cnparam :first cnparam-node :value cn))
60 (should (eq value-type 'ical:cal-address))
61 (should (equal value "mailto:hermes@planetexpress.com"))
62 (should (eq 'ical:cnparam (ical:ast-node-type cnparam-node)))
63 (should (equal cn "H. Conrad")))))
64
65(ert-deftest iat:make-property/list ()
66 "Test that `icalendar-make-property' works as documented with a list of values."
67 (let ((rdate-node (icalendar-make-property icalendar-rdate
68 (list '(2 1 2025) '(3 1 2025)))))
69 (should (ical:property-node-p rdate-node))
70 (should (eq 'ical:rdate (ical:ast-node-type rdate-node)))
71 (ical:with-property rdate-node
72 ((ical:valuetypeparam :first valtype-node :value valtype))
73 (should (and (listp value-nodes) (length= value-nodes 2)))
74 (should (seq-every-p
75 (lambda (n) (eq 'ical:date (ical:ast-node-type n)))
76 value-nodes))
77 (should (equal '(2 1 2025) (car values)))
78 (should (equal '(3 1 2025) (cadr values)))
79 (should (ical:ast-node-p valtype-node))
80 (should (eq 'ical:valuetypeparam (ical:ast-node-type valtype-node)))
81 (should (eq 'ical:date valtype)))))
82
83(ert-deftest iat:make-component ()
84 "Test that `icalendar-make-component' works as documented."
85 (let* ((others (list (icalendar-make-property ical:dtstart '(9 6 3003))
86 (icalendar-make-property ical:rrule '((FREQ DAILY)))))
87 (vevent-node (icalendar-make-component ical:vevent
88 (ical:summary "Party")
89 (ical:location "Robot House")
90 (@ others))))
91 (should (ical:component-node-p vevent-node))
92 (should (eq 'ical:vevent (ical:ast-node-type vevent-node)))
93 (ical:with-component vevent-node
94 ((ical:uid :first uid-node)
95 (ical:dtstamp :first dtstamp-node)
96 (ical:summary :value summary)
97 (ical:location :value location)
98 (ical:dtstart :first dtstart-node :value dtstart)
99 (ical:rrule :first rrule-node :value rrule))
100 (should (and (ical:ast-node-p uid-node)
101 (ical:ast-node-p dtstamp-node)))
102 (should (equal summary "Party"))
103 (should (equal location "Robot House"))
104 (should (equal dtstart '(9 6 3003)))
105 (should (equal rrule '((FREQ DAILY)))))))
106
107;; TODO: properties, components too
108
109;; Local Variables:
110;; read-symbol-shorthands: (("iat:" . "icalendar-ast-test-") ("ical:" . "icalendar-"))
111;; End:
112;;; icalendar-ast-tests.el ends here
diff --git a/test/lisp/calendar/icalendar-parser-tests.el b/test/lisp/calendar/icalendar-parser-tests.el
new file mode 100644
index 00000000000..f3c5de35c87
--- /dev/null
+++ b/test/lisp/calendar/icalendar-parser-tests.el
@@ -0,0 +1,2032 @@
1;;; tests/icalendar-parser.el --- Tests for icalendar-parser -*- lexical-binding: t; -*-
2;; Copyright (C) 2025 Free Software Foundation, Inc.
3
4;; This file is part of GNU Emacs.
5
6;; GNU Emacs is free software: you can redistribute it and/or modify
7;; it under the terms of the GNU General Public License as published by
8;; the Free Software Foundation, either version 3 of the License, or
9;; (at your option) any later version.
10
11;; GNU Emacs is distributed in the hope that it will be useful,
12;; but WITHOUT ANY WARRANTY; without even the implied warranty of
13;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14;; GNU General Public License for more details.
15
16;; You should have received a copy of the GNU General Public License
17;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
18
19;;; Code:
20
21(eval-when-compile (require 'cl-lib))
22(eval-when-compile (require 'icalendar-macs))
23(require 'ert)
24(require 'ert-x)
25(require 'icalendar-parser)
26(require 'icalendar-utils)
27
28(cl-defmacro ipt:parse/print-test (string &key expected parser type printer source)
29 "Create a test which parses STRING, prints the resulting parse
30tree, and compares the printed version with STRING (or with
31EXPECTED, if given). If they are the same, the test passes.
32PARSER and PRINTER should be the parser and printer functions
33appropriate to STRING. TYPE, if given, should be the type of
34object PARSER is expected to parse; it will be passed as PARSER's
35first argument. SOURCE should be a symbol; it is used to name the
36test."
37 (let ((parser-form
38 (if type
39 `(funcall (function ,parser) (quote ,type) (point-max))
40 `(funcall (function ,parser) (point-max)))))
41 `(ert-deftest ,(intern (concat "ipt:parse/print-" (symbol-name source))) ()
42 ,(format "Parse and reprint example from `%s'; pass if they match" source)
43 (let* ((parse-buf (get-buffer-create "*iCalendar Parse Test*"))
44 (print-buf (get-buffer-create "*iCalendar Print Test*"))
45 (unparsed ,string)
46 (expected (or ,expected unparsed))
47 (printed nil))
48 (set-buffer parse-buf)
49 (erase-buffer)
50 (insert unparsed)
51 (goto-char (point-min))
52 (let ((parsed ,parser-form))
53 (should (icalendar-ast-node-valid-p parsed))
54 (set-buffer print-buf)
55 (erase-buffer)
56 (insert (funcall (function ,printer) parsed))
57 ;; this may need adjusting if printers become coding-system aware:
58 (decode-coding-region (point-min) (point-max) 'utf-8-dos)
59 (setq printed (buffer-substring-no-properties (point-min) (point-max)))
60 (should (equal expected printed)))))))
61
62(ipt:parse/print-test
63"ATTENDEE;RSVP=TRUE;ROLE=REQ-PARTICIPANT:mailto:jsmith@example.com\n"
64:parser icalendar-parse-property
65:printer icalendar-print-property-node
66:source rfc5545-sec3.1.1/1)
67
68(ipt:parse/print-test
69"RDATE;VALUE=DATE:19970304,19970504,19970704,19970904\n"
70:parser icalendar-parse-property
71:printer icalendar-print-property-node
72:source rfc5545-sec3.1.1/2)
73
74(ipt:parse/print-test
75"ATTACH:http://example.com/public/quarterly-report.doc\n"
76:parser icalendar-parse-property
77:printer icalendar-print-property-node
78:source rfc5545-sec3.1.3/1)
79
80(ipt:parse/print-test
81;; Corrected. The original contains invalid base64 data; it was
82;; missing the final "=", as noted in errata ID 5602.
83;; The decoded string should read:
84;; The quick brown fox jumps over the lazy dog.
85"ATTACH;FMTTYPE=text/plain;ENCODING=BASE64;VALUE=BINARY:VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4=\n"
86:parser icalendar-parse-property
87:printer icalendar-print-property-node
88:source rfc5545-sec3.1.3/2)
89
90(ipt:parse/print-test
91"DESCRIPTION;ALTREP=\"cid:part1.0001@example.org\":The Fall'98 Wild Wizards Conference - - Las Vegas\\, NV\\, USA\n"
92:parser icalendar-parse-property
93:printer icalendar-print-property-node
94:source rfc5545-sec3.2/1)
95
96(ipt:parse/print-test
97"DESCRIPTION;ALTREP=\"CID:part3.msg.970415T083000@example.com\": Project XYZ Review Meeting will include the following agenda items: (a) Market Overview\\, (b) Finances\\, (c) Project Management\n"
98:parser icalendar-parse-property
99:printer icalendar-print-property-node
100:source rfc5545-sec3.2.1/1)
101
102(ipt:parse/print-test
103"ORGANIZER;CN=\"John Smith\":mailto:jsmith@example.com\n"
104;; CN param value does not require quotes, so they're missing when
105;; re-printed:
106:expected "ORGANIZER;CN=John Smith:mailto:jsmith@example.com\n"
107:parser icalendar-parse-property
108:printer icalendar-print-property-node
109:source rfc5545-sec3.2.2/1)
110
111(ipt:parse/print-test
112"ATTENDEE;CUTYPE=GROUP:mailto:ietf-calsch@example.org\n"
113:parser icalendar-parse-property
114:printer icalendar-print-property-node
115:source rfc5545-sec3.2.3/1)
116
117(ipt:parse/print-test
118"ATTENDEE;DELEGATED-FROM=\"mailto:jsmith@example.com\":mailto:jdoe@example.com\n"
119:parser icalendar-parse-property
120:printer icalendar-print-property-node
121:source rfc5545-sec3.2.4/1)
122
123(ipt:parse/print-test
124"ATTENDEE;DELEGATED-TO=\"mailto:jdoe@example.com\",\"mailto:jqpublic@example.com\":mailto:jsmith@example.com\n"
125:parser icalendar-parse-property
126:printer icalendar-print-property-node
127:source rfc5545-sec3.2.5/1)
128
129(ipt:parse/print-test
130"ORGANIZER;DIR=\"ldap://example.com:6666/o=ABC%20Industries,c=US???(cn=Jim%20Dolittle)\":mailto:jimdo@example.com\n"
131:parser icalendar-parse-property
132:printer icalendar-print-property-node
133:source rfc5545-sec3.2.6/1)
134
135(ipt:parse/print-test
136"ATTACH;FMTTYPE=text/plain;ENCODING=BASE64;VALUE=BINARY:TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2ljaW5nIGVsaXQsIHNlZCBkbyBlaXVzbW9kIHRlbXBvciBpbmNpZGlkdW50IHV0IGxhYm9yZSBldCBkb2xvcmUgbWFnbmEgYWxpcXVhLiBVdCBlbmltIGFkIG1pbmltIHZlbmlhbSwgcXVpcyBub3N0cnVkIGV4ZXJjaXRhdGlvbiB1bGxhbWNvIGxhYm9yaXMgbmlzaSB1dCBhbGlxdWlwIGV4IGVhIGNvbW1vZG8gY29uc2VxdWF0LiBEdWlzIGF1dGUgaXJ1cmUgZG9sb3IgaW4gcmVwcmVoZW5kZXJpdCBpbiB2b2x1cHRhdGUgdmVsaXQgZXNzZSBjaWxsdW0gZG9sb3JlIGV1IGZ1Z2lhdCBudWxsYSBwYXJpYXR1ci4gRXhjZXB0ZXVyIHNpbnQgb2NjYWVjYXQgY3VwaWRhdGF0IG5vbiBwcm9pZGVudCwgc3VudCBpbiBjdWxwYSBxdWkgb2ZmaWNpYSBkZXNlcnVudCBtb2xsaXQgYW5pbSBpZCBlc3QgbGFib3J1bS4=\n"
137:parser icalendar-parse-property
138:printer icalendar-print-property-node
139:source rfc5545-sec3.2.7/1)
140
141(ipt:parse/print-test
142"ATTACH;FMTTYPE=application/msword:ftp://example.com/pub/docs/agenda.doc\n"
143:parser icalendar-parse-property
144:printer icalendar-print-property-node
145:source rfc5545-sec3.2.8/1)
146
147(ipt:parse/print-test
148"FREEBUSY;FBTYPE=BUSY:19980415T133000Z/19980415T170000Z\n"
149:parser icalendar-parse-property
150:printer icalendar-print-property-node
151:source rfc5545-sec3.2.9/1)
152
153(ipt:parse/print-test
154"SUMMARY;LANGUAGE=en-US:Company Holiday Party\n"
155:parser icalendar-parse-property
156:printer icalendar-print-property-node
157:source rfc5545-sec3.2.10/1)
158
159(ipt:parse/print-test
160"LOCATION;LANGUAGE=en:Germany\n"
161:parser icalendar-parse-property
162:printer icalendar-print-property-node
163:source rfc5545-sec3.2.10/2)
164
165(ipt:parse/print-test
166"LOCATION;LANGUAGE=no:Tyskland\n"
167:parser icalendar-parse-property
168:printer icalendar-print-property-node
169:source rfc5545-sec3.2.10/3)
170
171(ipt:parse/print-test
172"ATTENDEE;MEMBER=\"mailto:ietf-calsch@example.org\":mailto:jsmith@example.com\n"
173:parser icalendar-parse-property
174:printer icalendar-print-property-node
175:source rfc5545-sec3.2.11/1)
176
177(ipt:parse/print-test
178"ATTENDEE;MEMBER=\"mailto:projectA@example.com\",\"mailto:projectB@example.com\":mailto:janedoe@example.com\n"
179:parser icalendar-parse-property
180:printer icalendar-print-property-node
181:source rfc5545-sec3.2.11/2)
182
183(ipt:parse/print-test
184"ATTENDEE;PARTSTAT=DECLINED:mailto:jsmith@example.com\n"
185:parser icalendar-parse-property
186:printer icalendar-print-property-node
187:source rfc5545-sec3.2.12/1)
188
189(ipt:parse/print-test
190"RECURRENCE-ID;RANGE=THISANDFUTURE:19980401T133000Z\n"
191:parser icalendar-parse-property
192:printer icalendar-print-property-node
193:source rfc5545-sec3.2.13/1)
194
195(ipt:parse/print-test
196"TRIGGER;RELATED=END:PT5M\n"
197:parser icalendar-parse-property
198:printer icalendar-print-property-node
199:source rfc5545-sec3.2.14/1)
200
201(ipt:parse/print-test
202"RELATED-TO;RELTYPE=SIBLING:19960401-080045-4000F192713@example.com\n"
203:parser icalendar-parse-property
204:printer icalendar-print-property-node
205:source rfc5545-sec3.2.15/1)
206
207(ipt:parse/print-test
208"ATTENDEE;ROLE=CHAIR:mailto:mrbig@example.com\n"
209:parser icalendar-parse-property
210:printer icalendar-print-property-node
211:source rfc5545-sec3.2.16/1)
212
213(ipt:parse/print-test
214"ATTENDEE;RSVP=TRUE:mailto:jsmith@example.com\n"
215:parser icalendar-parse-property
216:printer icalendar-print-property-node
217:source rfc5545-sec3.2.17/1)
218
219(ipt:parse/print-test
220"ORGANIZER;SENT-BY=\"mailto:sray@example.com\":mailto:jsmith@example.com\n"
221:parser icalendar-parse-property
222:printer icalendar-print-property-node
223:source rfc5545-sec3.2.18/1)
224
225(ipt:parse/print-test
226"DTSTART;TZID=America/New_York:19980119T020000\n"
227:parser icalendar-parse-property
228:printer icalendar-print-property-node
229:source rfc5545-sec3.2.19/1)
230
231(ipt:parse/print-test
232"DTEND;TZID=America/New_York:19980119T030000\n"
233:parser icalendar-parse-property
234:printer icalendar-print-property-node
235:source rfc5545-sec3.2.19/2)
236
237(ipt:parse/print-test
238"ATTACH;FMTTYPE=image/vnd.microsoft.icon;ENCODING=BASE64;VALUE=BINARY:AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAgIAAAICAgADAwMAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMwAAAAAAABNEMQAAAAAAAkQgAAAAAAJEREQgAAACECQ0QgEgAAQxQzM0E0AABERCRCREQAADRDJEJEQwAAAhA0QwEQAAAAAEREAAAAAAAAREQAAAAAAAAkQgAAAAAAAAMgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
239:parser icalendar-parse-property
240:printer icalendar-print-property-node
241:source rfc5545-sec3.3.1/1)
242
243(ipt:parse/print-test
244"TRUE"
245:type icalendar-boolean
246:parser icalendar-parse-value-node
247:printer icalendar-print-value-node
248:source rfc5545-sec3.3.2/1)
249
250(ipt:parse/print-test
251"mailto:jane_doe@example.com"
252:type icalendar-cal-address
253:parser icalendar-parse-value-node
254:printer icalendar-print-value-node
255:source rfc5545-sec3.3.3/1)
256
257(ipt:parse/print-test
258"19970714"
259:type icalendar-date
260:parser icalendar-parse-value-node
261:printer icalendar-print-value-node
262:source rfc5545-sec3.3.4/1)
263
264(ipt:parse/print-test
265;; 'Floating' time:
266"19980118T230000"
267:type icalendar-date-time
268:parser icalendar-parse-value-node
269:printer icalendar-print-value-node
270:source rfc5545-sec3.3.5/1)
271
272(ipt:parse/print-test
273;; UTC time:
274"19980119T070000Z"
275:type icalendar-date-time
276:parser icalendar-parse-value-node
277:printer icalendar-print-value-node
278:source rfc5545-sec3.3.5/2)
279
280(ipt:parse/print-test
281;; Leap second (seconds = 60)
282"19970630T235960Z"
283:type icalendar-date-time
284:parser icalendar-parse-value-node
285:printer icalendar-print-value-node
286:source rfc5545-sec3.3.5/3)
287
288(ipt:parse/print-test
289;; Local time:
290"DTSTART:19970714T133000\n"
291:parser icalendar-parse-property
292:printer icalendar-print-property-node
293:source rfc5545-sec3.3.5/4)
294
295(ipt:parse/print-test
296;; UTC time:
297"DTSTART:19970714T173000Z\n"
298:parser icalendar-parse-property
299:printer icalendar-print-property-node
300:source rfc5545-sec3.3.5/5)
301
302(ipt:parse/print-test
303;; Local time with TZ identifier:
304"DTSTART;TZID=America/New_York:19970714T133000\n"
305:parser icalendar-parse-property
306:printer icalendar-print-property-node
307:source rfc5545-sec3.3.5/6)
308
309(ipt:parse/print-test
310"P15DT5H0M20S"
311:expected "P15DT5H20S"
312:type icalendar-dur-value
313:parser icalendar-parse-value-node
314:printer icalendar-print-value-node
315:source rfc5545-sec3.3.6/1)
316
317(ipt:parse/print-test
318"P7W"
319:type icalendar-dur-value
320:parser icalendar-parse-value-node
321:printer icalendar-print-value-node
322:source rfc5545-sec3.3.6/2)
323
324(ipt:parse/print-test
325"1000000.0000001"
326:type icalendar-float
327:parser icalendar-parse-value-node
328:printer icalendar-print-value-node
329:source rfc5545-sec3.3.7/1)
330
331(ipt:parse/print-test
332"1.333"
333:type icalendar-float
334:parser icalendar-parse-value-node
335:printer icalendar-print-value-node
336:source rfc5545-sec3.3.7/2)
337
338(ipt:parse/print-test
339"-3.14"
340:type icalendar-float
341:parser icalendar-parse-value-node
342:printer icalendar-print-value-node
343:source rfc5545-sec3.3.7/3)
344
345(ipt:parse/print-test
346"1234567890"
347:type icalendar-integer
348:parser icalendar-parse-value-node
349:printer icalendar-print-value-node
350:source rfc5545-sec3.3.8/1)
351
352(ipt:parse/print-test
353"-1234567890"
354:type icalendar-integer
355:parser icalendar-parse-value-node
356:printer icalendar-print-value-node
357:source rfc5545-sec3.3.8/2)
358
359(ipt:parse/print-test
360"+1234567890"
361;; "+" sign isn't required, so it's not re-printed:
362:expected "1234567890"
363:type icalendar-integer
364:parser icalendar-parse-value-node
365:printer icalendar-print-value-node
366:source rfc5545-sec3.3.8/3)
367
368(ipt:parse/print-test
369"432109876"
370:type icalendar-integer
371:parser icalendar-parse-value-node
372:printer icalendar-print-value-node
373:source rfc5545-sec3.3.8/4)
374
375(ipt:parse/print-test
376"19970101T180000Z/19970102T070000Z"
377:type icalendar-period
378:parser icalendar-parse-value-node
379:printer icalendar-print-value-node
380:source rfc5545-sec3.3.9/1)
381
382(ipt:parse/print-test
383"19970101T180000Z/PT5H30M"
384:type icalendar-period
385:parser icalendar-parse-value-node
386:printer icalendar-print-value-node
387:source rfc5545-sec3.3.9/2)
388
389(ipt:parse/print-test
390"FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1"
391:type icalendar-recur
392:parser icalendar-parse-value-node
393:printer icalendar-print-value-node
394:source rfc5545-sec3.3.10/1)
395
396(ipt:parse/print-test
397"FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30"
398:type icalendar-recur
399:parser icalendar-parse-value-node
400:printer icalendar-print-value-node
401:source rfc5545-sec3.3.10/2)
402
403(ipt:parse/print-test
404"FREQ=DAILY;COUNT=10;INTERVAL=2"
405:type icalendar-recur
406:parser icalendar-parse-value-node
407:printer icalendar-print-value-node
408:source rfc5545-sec3.3.10/3)
409
410(ipt:parse/print-test
411"Project XYZ Final Review\\nConference Room - 3B\\nCome Prepared."
412:type icalendar-text
413:parser icalendar-parse-value-node
414:printer icalendar-print-value-node
415:source rfc5545-sec3.3.11/1)
416
417(ipt:parse/print-test
418;; Local time:
419"230000"
420:type icalendar-time
421:parser icalendar-parse-value-node
422:printer icalendar-print-value-node
423:source rfc5545-sec3.3.12/1)
424
425(ipt:parse/print-test
426;; UTC time:
427"070000Z"
428:type icalendar-time
429:parser icalendar-parse-value-node
430:printer icalendar-print-value-node
431:source rfc5545-sec3.3.12/2)
432
433(ipt:parse/print-test
434;; Local time:
435"083000"
436:type icalendar-time
437:parser icalendar-parse-value-node
438:printer icalendar-print-value-node
439:source rfc5545-sec3.3.12/3)
440
441(ipt:parse/print-test
442;; UTC time:
443"133000Z"
444:type icalendar-time
445:parser icalendar-parse-value-node
446:printer icalendar-print-value-node
447:source rfc5545-sec3.3.12/4)
448
449(ipt:parse/print-test
450;; Local time with TZ identifier:
451"SOMETIMEPROP;TZID=America/New_York;VALUE=TIME:083000\n"
452:parser icalendar-parse-property
453:printer icalendar-print-property-node
454:source rfc5545-sec3.3.12/5)
455
456(ipt:parse/print-test
457"http://example.com/my-report.txt"
458:type icalendar-uri
459:parser icalendar-parse-value-node
460:printer icalendar-print-value-node
461:source rfc5545-sec3.3.13/1)
462
463(ipt:parse/print-test
464"-0500"
465:type icalendar-utc-offset
466:parser icalendar-parse-value-node
467:printer icalendar-print-value-node
468:source rfc5545-sec3.3.14/1)
469
470(ipt:parse/print-test
471"+0100"
472:type icalendar-utc-offset
473:parser icalendar-parse-value-node
474:printer icalendar-print-value-node
475:source rfc55453.3.14/1)
476
477(ipt:parse/print-test
478"BEGIN:VCALENDAR
479VERSION:2.0
480PRODID:-//hacksw/handcal//NONSGML v1.0//EN
481BEGIN:VEVENT
482UID:19970610T172345Z-AF23B2@example.com
483DTSTAMP:19970610T172345Z
484DTSTART:19970714T170000Z
485DTEND:19970715T040000Z
486SUMMARY:Bastille Day Party
487END:VEVENT
488END:VCALENDAR
489"
490:parser icalendar-parse-calendar
491:printer icalendar-print-calendar-node
492:source rfc5545-sec3.4/1)
493
494(ipt:parse/print-test
495"DTSTART:19960415T133000Z\n"
496:parser icalendar-parse-property
497:printer icalendar-print-property-node
498:source rfc5545-sec3.5/1)
499
500(ipt:parse/print-test
501"BEGIN:VEVENT
502UID:19970901T130000Z-123401@example.com
503DTSTAMP:19970901T130000Z
504DTSTART:19970903T163000Z
505DTEND:19970903T190000Z
506SUMMARY:Annual Employee Review
507CLASS:PRIVATE
508CATEGORIES:BUSINESS,HUMAN RESOURCES
509END:VEVENT
510"
511:parser icalendar-parse-component
512:printer icalendar-print-component-node
513:source rfc5545-sec3.6.1/1)
514
515(ipt:parse/print-test
516"BEGIN:VEVENT
517UID:19970901T130000Z-123402@example.com
518DTSTAMP:19970901T130000Z
519DTSTART:19970401T163000Z
520DTEND:19970402T010000Z
521SUMMARY:Laurel is in sensitivity awareness class.
522CLASS:PUBLIC
523CATEGORIES:BUSINESS,HUMAN RESOURCES
524TRANSP:TRANSPARENT
525END:VEVENT
526"
527:parser icalendar-parse-component
528:printer icalendar-print-component-node
529:source rfc5545-sec3.6.1/2)
530
531(ipt:parse/print-test
532"BEGIN:VEVENT
533UID:19970901T130000Z-123403@example.com
534DTSTAMP:19970901T130000Z
535DTSTART;VALUE=DATE:19971102
536SUMMARY:Our Blissful Anniversary
537TRANSP:TRANSPARENT
538CLASS:CONFIDENTIAL
539CATEGORIES:ANNIVERSARY,PERSONAL,SPECIAL OCCASION
540RRULE:FREQ=YEARLY
541END:VEVENT
542"
543:parser icalendar-parse-component
544:printer icalendar-print-component-node
545:source rfc5545-sec3.6.1/3)
546
547(ipt:parse/print-test
548"BEGIN:VEVENT
549UID:20070423T123432Z-541111@example.com
550DTSTAMP:20070423T123432Z
551DTSTART;VALUE=DATE:20070628
552DTEND;VALUE=DATE:20070709
553SUMMARY:Festival International de Jazz de Montreal
554TRANSP:TRANSPARENT
555END:VEVENT
556"
557:parser icalendar-parse-component
558:printer icalendar-print-component-node
559:source rfc5545-sec3.6.1/4)
560
561(ipt:parse/print-test
562"BEGIN:VTODO
563UID:20070313T123432Z-456553@example.com
564DTSTAMP:20070313T123432Z
565DUE;VALUE=DATE:20070501
566SUMMARY:Submit Quebec Income Tax Return for 2006
567CLASS:CONFIDENTIAL
568CATEGORIES:FAMILY,FINANCE
569STATUS:NEEDS-ACTION
570END:VTODO
571"
572:parser icalendar-parse-component
573:printer icalendar-print-component-node
574:source rfc5545-sec3.6.2/1)
575
576(ipt:parse/print-test
577"BEGIN:VTODO
578UID:20070514T103211Z-123404@example.com
579DTSTAMP:20070514T103211Z
580DTSTART:20070514T110000Z
581DUE:20070709T130000Z
582COMPLETED:20070707T100000Z
583SUMMARY:Submit Revised Internet-Draft
584PRIORITY:1
585STATUS:NEEDS-ACTION
586END:VTODO
587"
588:parser icalendar-parse-component
589:printer icalendar-print-component-node
590:source rfc5545-sec3.6.2/2)
591
592(ipt:parse/print-test
593"BEGIN:VJOURNAL
594UID:19970901T130000Z-123405@example.com
595DTSTAMP:19970901T130000Z
596DTSTART;VALUE=DATE:19970317
597SUMMARY:Staff meeting minutes
598DESCRIPTION:1. Staff meeting: Participants include Joe\\,Lisa\\, and Bob. Aurora project plans were reviewed. There is currently no budget reserves for this project. Lisa will escalate to management. Next meeting on Tuesday.\\n 2. Telephone Conference: ABC Corp. sales representative called to discuss new printer. Promised to get us a demo by Friday.\\n3. Henry Miller (Handsoff Insurance): Car was totaled by tree. Is looking into a loaner car. 555-2323 (tel).
599END:VJOURNAL
600"
601:parser icalendar-parse-component
602:printer icalendar-print-component-node
603:source rfc5545-sec3.6.3/1)
604
605(ipt:parse/print-test
606"BEGIN:VFREEBUSY
607UID:19970901T082949Z-FA43EF@example.com
608ORGANIZER:mailto:jane_doe@example.com
609ATTENDEE:mailto:john_public@example.com
610DTSTART:19971015T050000Z
611DTEND:19971016T050000Z
612DTSTAMP:19970901T083000Z
613END:VFREEBUSY
614"
615:parser icalendar-parse-component
616:printer icalendar-print-component-node
617:source rfc5545-sec3.6.4/1)
618
619(ipt:parse/print-test
620"BEGIN:VFREEBUSY
621UID:19970901T095957Z-76A912@example.com
622ORGANIZER:mailto:jane_doe@example.com
623ATTENDEE:mailto:john_public@example.com
624DTSTAMP:19970901T100000Z
625FREEBUSY:19971015T050000Z/PT8H30M,19971015T160000Z/PT5H30M,19971015T223000Z/PT6H30M
626URL:http://example.com/pub/busy/jpublic-01.ifb
627COMMENT:This iCalendar file contains busy time information for the next three months.
628END:VFREEBUSY
629"
630:parser icalendar-parse-component
631:printer icalendar-print-component-node
632:source rfc5545-sec3.6.4/2)
633
634(ipt:parse/print-test
635;; Corrected. Original has invalid value in ORGANIZER
636"BEGIN:VFREEBUSY
637UID:19970901T115957Z-76A912@example.com
638DTSTAMP:19970901T120000Z
639ORGANIZER:mailto:jsmith@example.com
640DTSTART:19980313T141711Z
641DTEND:19980410T141711Z
642FREEBUSY:19980314T233000Z/19980315T003000Z
643FREEBUSY:19980316T153000Z/19980316T163000Z
644FREEBUSY:19980318T030000Z/19980318T040000Z
645URL:http://www.example.com/calendar/busytime/jsmith.ifb
646END:VFREEBUSY
647"
648:parser icalendar-parse-component
649:printer icalendar-print-component-node
650:source rfc5545-sec3.6.4/3)
651
652(ipt:parse/print-test
653"BEGIN:VTIMEZONE
654TZID:America/New_York
655LAST-MODIFIED:20050809T050000Z
656BEGIN:DAYLIGHT
657DTSTART:19670430T020000
658RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=-1SU;UNTIL=19730429T070000Z
659TZOFFSETFROM:-0500
660TZOFFSETTO:-0400
661TZNAME:EDT
662END:DAYLIGHT
663BEGIN:STANDARD
664DTSTART:19671029T020000
665RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=20061029T060000Z
666TZOFFSETFROM:-0400
667TZOFFSETTO:-0500
668TZNAME:EST
669END:STANDARD
670BEGIN:DAYLIGHT
671DTSTART:19740106T020000
672RDATE:19750223T020000
673TZOFFSETFROM:-0500
674TZOFFSETTO:-0400
675TZNAME:EDT
676END:DAYLIGHT
677BEGIN:DAYLIGHT
678DTSTART:19760425T020000
679RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=-1SU;UNTIL=19860427T070000Z
680TZOFFSETFROM:-0500
681TZOFFSETTO:-0400
682TZNAME:EDT
683END:DAYLIGHT
684BEGIN:DAYLIGHT
685DTSTART:19870405T020000
686RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU;UNTIL=20060402T070000Z
687TZOFFSETFROM:-0500
688TZOFFSETTO:-0400
689TZNAME:EDT
690END:DAYLIGHT
691BEGIN:DAYLIGHT
692DTSTART:20070311T020000
693RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
694TZOFFSETFROM:-0500
695TZOFFSETTO:-0400
696TZNAME:EDT
697END:DAYLIGHT
698BEGIN:STANDARD
699DTSTART:20071104T020000
700RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
701TZOFFSETFROM:-0400
702TZOFFSETTO:-0500
703TZNAME:EST
704END:STANDARD
705END:VTIMEZONE
706"
707:parser icalendar-parse-component
708:printer icalendar-print-component-node
709:source rfc5545-sec3.6.5/1)
710
711(ipt:parse/print-test
712"BEGIN:VTIMEZONE
713TZID:America/New_York
714LAST-MODIFIED:20050809T050000Z
715BEGIN:STANDARD
716DTSTART:20071104T020000
717TZOFFSETFROM:-0400
718TZOFFSETTO:-0500
719TZNAME:EST
720END:STANDARD
721BEGIN:DAYLIGHT
722DTSTART:20070311T020000
723TZOFFSETFROM:-0500
724TZOFFSETTO:-0400
725TZNAME:EDT
726END:DAYLIGHT
727END:VTIMEZONE
728"
729:parser icalendar-parse-component
730:printer icalendar-print-component-node
731:source rfc5545-sec3.6.5/2)
732
733(ipt:parse/print-test
734"BEGIN:VTIMEZONE
735TZID:America/New_York
736LAST-MODIFIED:20050809T050000Z
737TZURL:http://zones.example.com/tz/America-New_York.ics
738BEGIN:STANDARD
739DTSTART:20071104T020000
740RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
741TZOFFSETFROM:-0400
742TZOFFSETTO:-0500
743TZNAME:EST
744END:STANDARD
745BEGIN:DAYLIGHT
746DTSTART:20070311T020000
747RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
748TZOFFSETFROM:-0500
749TZOFFSETTO:-0400
750TZNAME:EDT
751END:DAYLIGHT
752END:VTIMEZONE
753"
754:parser icalendar-parse-component
755:printer icalendar-print-component-node
756:source rfc5545-sec3.6.5/3)
757
758(ipt:parse/print-test
759"BEGIN:VTIMEZONE
760TZID:Fictitious
761LAST-MODIFIED:19870101T000000Z
762BEGIN:STANDARD
763DTSTART:19671029T020000
764RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
765TZOFFSETFROM:-0400
766TZOFFSETTO:-0500
767TZNAME:EST
768END:STANDARD
769BEGIN:DAYLIGHT
770DTSTART:19870405T020000
771RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4;UNTIL=19980404T070000Z
772TZOFFSETFROM:-0500
773TZOFFSETTO:-0400
774TZNAME:EDT
775END:DAYLIGHT
776END:VTIMEZONE
777"
778:parser icalendar-parse-component
779:printer icalendar-print-component-node
780:source rfc5545-sec3.6.5/4)
781
782(ipt:parse/print-test
783"BEGIN:VTIMEZONE
784TZID:Fictitious
785LAST-MODIFIED:19870101T000000Z
786BEGIN:STANDARD
787DTSTART:19671029T020000
788RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
789TZOFFSETFROM:-0400
790TZOFFSETTO:-0500
791TZNAME:EST
792END:STANDARD
793BEGIN:DAYLIGHT
794DTSTART:19870405T020000
795RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4;UNTIL=19980404T070000Z
796TZOFFSETFROM:-0500
797TZOFFSETTO:-0400
798TZNAME:EDT
799END:DAYLIGHT
800BEGIN:DAYLIGHT
801DTSTART:19990424T020000
802RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=4
803TZOFFSETFROM:-0500
804TZOFFSETTO:-0400
805TZNAME:EDT
806END:DAYLIGHT
807END:VTIMEZONE
808"
809:parser icalendar-parse-component
810:printer icalendar-print-component-node
811:source rfc5545-sec3.6.5/5)
812
813(ipt:parse/print-test
814"BEGIN:VALARM
815TRIGGER;VALUE=DATE-TIME:19970317T133000Z
816REPEAT:4
817DURATION:PT15M
818ACTION:AUDIO
819ATTACH;FMTTYPE=audio/basic:ftp://example.com/pub/sounds/bell-01.aud
820END:VALARM
821"
822:parser icalendar-parse-component
823:printer icalendar-print-component-node
824:source rfc5545-sec3.6.6/1)
825
826(ipt:parse/print-test
827"BEGIN:VALARM
828TRIGGER:-PT30M
829REPEAT:2
830DURATION:PT15M
831ACTION:DISPLAY
832DESCRIPTION:Breakfast meeting with executive\\nteam at 8:30 AM EST.
833END:VALARM
834"
835:parser icalendar-parse-component
836:printer icalendar-print-component-node
837:source rfc5545-sec3.6.6/2)
838
839(ipt:parse/print-test
840"BEGIN:VALARM
841TRIGGER;RELATED=END:-P2D
842ACTION:EMAIL
843ATTENDEE:mailto:john_doe@example.com
844SUMMARY:*** REMINDER: SEND AGENDA FOR WEEKLY STAFF MEETING ***
845DESCRIPTION:A draft agenda needs to be sent out to the attendees to the weekly managers meeting (MGR-LIST). Attached is a pointer the document template for the agenda file.
846ATTACH;FMTTYPE=application/msword:http://example.com/templates/agenda.doc
847END:VALARM
848"
849:parser icalendar-parse-component
850:printer icalendar-print-component-node
851:source rfc5545-sec3.6.6/3)
852
853(ipt:parse/print-test
854"CALSCALE:GREGORIAN\n"
855:parser icalendar-parse-property
856:printer icalendar-print-property-node
857:source rfc5545-sec3.7.1/1)
858
859(ipt:parse/print-test
860"METHOD:REQUEST\n"
861:parser icalendar-parse-property
862:printer icalendar-print-property-node
863:source rfc5545-sec3.7.2/1)
864
865(ipt:parse/print-test
866"PRODID:-//ABC Corporation//NONSGML My Product//EN\n"
867:parser icalendar-parse-property
868:printer icalendar-print-property-node
869:source rfc5545-sec3.7.3/1)
870
871(ipt:parse/print-test
872"VERSION:2.0\n"
873:parser icalendar-parse-property
874:printer icalendar-print-property-node
875:source rfc5545-sec3.7./1)
876
877(ipt:parse/print-test
878"ATTACH:CID:jsmith.part3.960817T083000.xyzMail@example.com\n"
879:parser icalendar-parse-property
880:printer icalendar-print-property-node
881:source rfc5545-sec3.8.1.1/1)
882
883(ipt:parse/print-test
884"ATTACH;FMTTYPE=application/postscript:ftp://example.com/pub/reports/r-960812.ps\n"
885:parser icalendar-parse-property
886:printer icalendar-print-property-node
887:source rfc5545-sec3.8.1.1/2)
888
889(ipt:parse/print-test
890"CATEGORIES:APPOINTMENT,EDUCATION\n"
891:parser icalendar-parse-property
892:printer icalendar-print-property-node
893:source rfc5545-sec3.8.1.2/1)
894
895(ipt:parse/print-test
896"CATEGORIES:MEETING\n"
897:parser icalendar-parse-property
898:printer icalendar-print-property-node
899:source rfc5545-sec3.8.1.2/2)
900
901(ipt:parse/print-test
902"CLASS:PUBLIC\n"
903:parser icalendar-parse-property
904:printer icalendar-print-property-node
905:source rfc5545-sec3.8.1.3/1)
906
907(ipt:parse/print-test
908"COMMENT:The meeting really needs to include both ourselves and the customer. We can't hold this meeting without them. As a matter of fact\\, the venue for the meeting ought to be at their site. - - John\n"
909:parser icalendar-parse-property
910:printer icalendar-print-property-node
911:source rfc5545-sec3.8.1.4/1)
912
913(ipt:parse/print-test
914"DESCRIPTION:Meeting to provide technical review for \"Phoenix\" design.\\nHappy Face Conference Room. Phoenix design team MUST attend this meeting.\\nRSVP to team leader.\n"
915:parser icalendar-parse-property
916:printer icalendar-print-property-node
917:source rfc5545-sec3.8.1.5/1)
918
919(ipt:parse/print-test
920"GEO:37.386013;-122.082932\n"
921:parser icalendar-parse-property
922:printer icalendar-print-property-node
923:source rfc5545-sec3.8.1.6/1)
924
925(ipt:parse/print-test
926"LOCATION:Conference Room - F123\\, Bldg. 002\n"
927:parser icalendar-parse-property
928:printer icalendar-print-property-node
929:source rfc5545-sec3.8.1.7/1)
930
931(ipt:parse/print-test
932"LOCATION;ALTREP=\"http://xyzcorp.com/conf-rooms/f123.vcf\":Conference Room - F123\\, Bldg. 002\n"
933:parser icalendar-parse-property
934:printer icalendar-print-property-node
935:source rfc5545-sec3.8.1.7/2)
936
937(ipt:parse/print-test
938"PERCENT-COMPLETE:39\n"
939:parser icalendar-parse-property
940:printer icalendar-print-property-node
941:source rfc5545-sec3.8.1.8/1)
942
943(ipt:parse/print-test
944"PRIORITY:1\n"
945:parser icalendar-parse-property
946:printer icalendar-print-property-node
947:source rfc5545-sec3.8.1.9/1)
948
949(ipt:parse/print-test
950"PRIORITY:2\n"
951:parser icalendar-parse-property
952:printer icalendar-print-property-node
953:source rfc5545-sec3.8.1.9/2)
954
955(ipt:parse/print-test
956"PRIORITY:0\n"
957:parser icalendar-parse-property
958:printer icalendar-print-property-node
959:source rfc5545-sec3.8.1.9/3)
960
961(ipt:parse/print-test
962"RESOURCES:EASEL,PROJECTOR,VCR\n"
963:parser icalendar-parse-property
964:printer icalendar-print-property-node
965:source rfc5545-sec3.8.1.10/1)
966
967(ipt:parse/print-test
968"RESOURCES;LANGUAGE=fr:Nettoyeur haute pression\n"
969:parser icalendar-parse-property
970:printer icalendar-print-property-node
971:source rfc5545-sec3.8.1.10/2)
972
973(ipt:parse/print-test
974"STATUS:TENTATIVE\n"
975:parser icalendar-parse-property
976:printer icalendar-print-property-node
977:source rfc5545-sec3.8.1.11/1)
978
979(ipt:parse/print-test
980"STATUS:NEEDS-ACTION\n"
981:parser icalendar-parse-property
982:printer icalendar-print-property-node
983:source rfc5545-sec3.8.1.11/2)
984
985(ipt:parse/print-test
986"STATUS:DRAFT\n"
987:parser icalendar-parse-property
988:printer icalendar-print-property-node
989:source rfc5545-sec3.8.1.11/3)
990
991(ipt:parse/print-test
992"SUMMARY:Department Party\n"
993:parser icalendar-parse-property
994:printer icalendar-print-property-node
995:source rfc5545-sec3.8.1.12/1)
996
997(ipt:parse/print-test
998"COMPLETED:19960401T150000Z\n"
999:parser icalendar-parse-property
1000:printer icalendar-print-property-node
1001:source rfc5545-sec3.8.2.1/1)
1002
1003(ipt:parse/print-test
1004"DTEND:19960401T150000Z\n"
1005:parser icalendar-parse-property
1006:printer icalendar-print-property-node
1007:source rfc5545-sec3.8.2.2/1)
1008
1009(ipt:parse/print-test
1010"DTEND;VALUE=DATE:19980704\n"
1011:parser icalendar-parse-property
1012:printer icalendar-print-property-node
1013:source rfc5545-sec3.8.2.2/2)
1014
1015(ipt:parse/print-test
1016"DUE:19980430T000000Z\n"
1017:parser icalendar-parse-property
1018:printer icalendar-print-property-node
1019:source rfc5545-sec3.8.2.3/1)
1020
1021(ipt:parse/print-test
1022"DTSTART:19980118T073000Z\n"
1023:parser icalendar-parse-property
1024:printer icalendar-print-property-node
1025:source rfc5545-sec3.8.2.4/1)
1026
1027(ipt:parse/print-test
1028"DURATION:PT1H0M0S\n"
1029;; 0M and 0S are not re-printed because they don't contribute to the duration:
1030:expected "DURATION:PT1H\n"
1031:parser icalendar-parse-property
1032:printer icalendar-print-property-node
1033:source rfc5545-sec3.8.2.5/1)
1034
1035(ipt:parse/print-test
1036"DURATION:PT15M\n"
1037:parser icalendar-parse-property
1038:printer icalendar-print-property-node
1039:source rfc5545-sec3.8.2.5/2)
1040
1041(ipt:parse/print-test
1042"FREEBUSY;FBTYPE=BUSY-UNAVAILABLE:19970308T160000Z/PT8H30M\n"
1043:parser icalendar-parse-property
1044:printer icalendar-print-property-node
1045:source rfc5545-sec3.8.2.6/1)
1046
1047(ipt:parse/print-test
1048"FREEBUSY;FBTYPE=FREE:19970308T160000Z/PT3H,19970308T200000Z/PT1H\n"
1049:parser icalendar-parse-property
1050:printer icalendar-print-property-node
1051:source rfc5545-sec3.8.2.6/2)
1052
1053(ipt:parse/print-test
1054"FREEBUSY;FBTYPE=FREE:19970308T160000Z/PT3H,19970308T200000Z/PT1H,19970308T230000Z/19970309T000000Z\n"
1055:parser icalendar-parse-property
1056:printer icalendar-print-property-node
1057:source rfc5545-sec3.8.2.6/3)
1058
1059(ipt:parse/print-test
1060"TRANSP:TRANSPARENT\n"
1061:parser icalendar-parse-property
1062:printer icalendar-print-property-node
1063:source rfc5545-sec3.8.2.7/1)
1064
1065(ipt:parse/print-test
1066"TRANSP:OPAQUE\n"
1067:parser icalendar-parse-property
1068:printer icalendar-print-property-node
1069:source rfc5545-sec3.8.2.7/2)
1070
1071(ipt:parse/print-test
1072"TZID:America/New_York\n"
1073:parser icalendar-parse-property
1074:printer icalendar-print-property-node
1075:source rfc5545-sec3.8.3.1/1)
1076
1077(ipt:parse/print-test
1078"TZID:America/New_York\n"
1079:parser icalendar-parse-property
1080:printer icalendar-print-property-node
1081:source rfc5545-sec3.8.3.1/2)
1082
1083(ipt:parse/print-test
1084"TZID:/example.org/America/New_York\n"
1085:parser icalendar-parse-property
1086:printer icalendar-print-property-node
1087:source rfc5545-sec3.8.3.1/3)
1088
1089(ipt:parse/print-test
1090"TZNAME:EST\n"
1091:parser icalendar-parse-property
1092:printer icalendar-print-property-node
1093:source rfc5545-sec3.8.3.2/1)
1094
1095(ipt:parse/print-test
1096"TZNAME;LANGUAGE=fr-CA:HNE\n"
1097:parser icalendar-parse-property
1098:printer icalendar-print-property-node
1099:source rfc5545-sec3.8.3.2/2)
1100
1101(ipt:parse/print-test
1102"TZOFFSETFROM:-0500\n"
1103:parser icalendar-parse-property
1104:printer icalendar-print-property-node
1105:source rfc5545-sec3.8.3.3/1)
1106
1107(ipt:parse/print-test
1108"TZOFFSETFROM:+1345\n"
1109:parser icalendar-parse-property
1110:printer icalendar-print-property-node
1111:source rfc5545-sec3.8.3.3/2)
1112
1113(ipt:parse/print-test
1114"TZOFFSETTO:-0400\n"
1115:parser icalendar-parse-property
1116:printer icalendar-print-property-node
1117:source rfc5545-sec3.8.3.4/1)
1118
1119(ipt:parse/print-test
1120"TZOFFSETTO:+1245\n"
1121:parser icalendar-parse-property
1122:printer icalendar-print-property-node
1123:source rfc5545-sec3.8.3.4/2)
1124
1125(ipt:parse/print-test
1126"TZURL:http://timezones.example.org/tz/America-Los_Angeles.ics\n"
1127:parser icalendar-parse-property
1128:printer icalendar-print-property-node
1129:source rfc5545-sec3.8.3.5/1)
1130
1131(ipt:parse/print-test
1132"ATTENDEE;MEMBER=\"mailto:DEV-GROUP@example.com\":mailto:joecool@example.com\n"
1133:parser icalendar-parse-property
1134:printer icalendar-print-property-node
1135:source rfc5545-sec3.8.4.1/1)
1136
1137(ipt:parse/print-test
1138"ATTENDEE;DELEGATED-FROM=\"mailto:immud@example.com\":mailto:ildoit@example.com\n"
1139:parser icalendar-parse-property
1140:printer icalendar-print-property-node
1141:source rfc5545-sec3.8.4.1/2)
1142
1143(ipt:parse/print-test
1144"ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=TENTATIVE;CN=Henry Cabot:mailto:hcabot@example.com\n"
1145:parser icalendar-parse-property
1146:printer icalendar-print-property-node
1147:source rfc5545-sec3.8.4.1/3)
1148
1149(ipt:parse/print-test
1150"ATTENDEE;ROLE=REQ-PARTICIPANT;DELEGATED-FROM=\"mailto:bob@example.com\";PARTSTAT=ACCEPTED;CN=Jane Doe:mailto:jdoe@example.com\n"
1151:parser icalendar-parse-property
1152:printer icalendar-print-property-node
1153:source rfc5545-sec3.8.4.1/4)
1154
1155(ipt:parse/print-test
1156"ATTENDEE;CN=John Smith;DIR=\"ldap://example.com:6666/o=ABC%20Industries,c=US???(cn=Jim%20Dolittle)\":mailto:jimdo@example.com\n"
1157:parser icalendar-parse-property
1158:printer icalendar-print-property-node
1159:source rfc5545-sec3.8.4.1/5)
1160
1161(ipt:parse/print-test
1162"ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=TENTATIVE;DELEGATED-FROM=\"mailto:iamboss@example.com\";CN=Henry Cabot:mailto:hcabot@example.com\n"
1163:parser icalendar-parse-property
1164:printer icalendar-print-property-node
1165:source rfc5545-sec3.8.4.1/6)
1166
1167(ipt:parse/print-test
1168"ATTENDEE;ROLE=NON-PARTICIPANT;PARTSTAT=DELEGATED;DELEGATED-TO=\"mailto:hcabot@example.com\";CN=The Big Cheese:mailto:iamboss@example.com\n"
1169:parser icalendar-parse-property
1170:printer icalendar-print-property-node
1171:source rfc5545-sec3.8.4.1/7)
1172
1173(ipt:parse/print-test
1174"ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=Jane Doe:mailto:jdoe@example.com\n"
1175:parser icalendar-parse-property
1176:printer icalendar-print-property-node
1177:source rfc5545-sec3.8.4.1/8)
1178
1179(ipt:parse/print-test
1180;; Corrected. Original lacks quotes around SENT-BY address.
1181"ATTENDEE;SENT-BY=\"mailto:jan_doe@example.com\";CN=John Smith:mailto:jsmith@example.com\n"
1182:parser icalendar-parse-property
1183:printer icalendar-print-property-node
1184:source rfc5545-sec3.8.4.1/9)
1185
1186(ipt:parse/print-test
1187"CONTACT:Jim Dolittle\\, ABC Industries\\, +1-919-555-1234\n"
1188:parser icalendar-parse-property
1189:printer icalendar-print-property-node
1190:source rfc5545-sec3.8.4.2/1)
1191
1192(ipt:parse/print-test
1193;; Corrected. Original contained unallowed backslash in ldap: URI
1194"CONTACT;ALTREP=\"ldap://example.com:6666/o=ABC%20Industries,c=US???(cn=Jim%20Dolittle)\":Jim Dolittle\\, ABC Industries\\,+1-919-555-1234\n"
1195:parser icalendar-parse-property
1196:printer icalendar-print-property-node
1197:source rfc5545-sec3.8.4.2/2)
1198
1199(ipt:parse/print-test
1200"CONTACT;ALTREP=\"CID:part3.msg970930T083000SILVER@example.com\":Jim Dolittle\\, ABC Industries\\, +1-919-555-1234\n"
1201:parser icalendar-parse-property
1202:printer icalendar-print-property-node
1203:source rfc5545-sec3.8.4.2/3)
1204
1205(ipt:parse/print-test
1206"CONTACT;ALTREP=\"http://example.com/pdi/jdoe.vcf\":Jim Dolittle\\, ABC Industries\\, +1-919-555-1234\n"
1207:parser icalendar-parse-property
1208:printer icalendar-print-property-node
1209:source rfc5545-sec3.8.4.2/4)
1210
1211(ipt:parse/print-test
1212"ORGANIZER;CN=John Smith:mailto:jsmith@example.com\n"
1213:parser icalendar-parse-property
1214:printer icalendar-print-property-node
1215:source rfc5545-sec3.8.4.3/1)
1216
1217(ipt:parse/print-test
1218"ORGANIZER;CN=JohnSmith;DIR=\"ldap://example.com:6666/o=DC%20Associates,c=US???(cn=John%20Smith)\":mailto:jsmith@example.com\n"
1219:parser icalendar-parse-property
1220:printer icalendar-print-property-node
1221:source rfc5545-sec3.8.4.3/2)
1222
1223(ipt:parse/print-test
1224"ORGANIZER;SENT-BY=\"mailto:jane_doe@example.com\":mailto:jsmith@example.com\n"
1225:parser icalendar-parse-property
1226:printer icalendar-print-property-node
1227:source rfc5545-sec3.8.4.3/3)
1228
1229(ipt:parse/print-test
1230"RECURRENCE-ID;VALUE=DATE:19960401\n"
1231:parser icalendar-parse-property
1232:printer icalendar-print-property-node
1233:source rfc5545-sec3.8.4.4/1)
1234
1235(ipt:parse/print-test
1236"RECURRENCE-ID;RANGE=THISANDFUTURE:19960120T120000Z\n"
1237:parser icalendar-parse-property
1238:printer icalendar-print-property-node
1239:source rfc5545-sec3.8.4.4/2)
1240
1241(ipt:parse/print-test
1242"RELATED-TO:jsmith.part7.19960817T083000.xyzMail@example.com\n"
1243:parser icalendar-parse-property
1244:printer icalendar-print-property-node
1245:source rfc5545-sec3.8.4.5/1)
1246
1247(ipt:parse/print-test
1248"RELATED-TO:19960401-080045-4000F192713-0052@example.com\n"
1249:parser icalendar-parse-property
1250:printer icalendar-print-property-node
1251:source rfc5545-sec3.8.4.5/2)
1252
1253(ipt:parse/print-test
1254"URL:http://example.com/pub/calendars/jsmith/mytime.ics\n"
1255:parser icalendar-parse-property
1256:printer icalendar-print-property-node
1257:source rfc5545-sec3.8.4.6/1)
1258
1259(ipt:parse/print-test
1260"UID:19960401T080045Z-4000F192713-0052@example.com\n"
1261:parser icalendar-parse-property
1262:printer icalendar-print-property-node
1263:source rfc5545-sec3.8.4.7/1)
1264
1265(ipt:parse/print-test
1266"EXDATE:19960402T010000Z,19960403T010000Z,19960404T010000Z\n"
1267:parser icalendar-parse-property
1268:printer icalendar-print-property-node
1269:source rfc5545-sec3.8.5.1/1)
1270
1271(ipt:parse/print-test
1272"RDATE:19970714T123000Z\n"
1273:parser icalendar-parse-property
1274:printer icalendar-print-property-node
1275:source rfc5545-sec3.8.5.2/1)
1276
1277(ipt:parse/print-test
1278"RDATE;TZID=America/New_York:19970714T083000\n"
1279:parser icalendar-parse-property
1280:printer icalendar-print-property-node
1281:source rfc5545-sec3.8.5.2/2)
1282
1283(ipt:parse/print-test
1284"RDATE;VALUE=PERIOD:19960403T020000Z/19960403T040000Z,19960404T010000Z/PT3H\n"
1285:parser icalendar-parse-property
1286:printer icalendar-print-property-node
1287:source rfc5545-sec3.8.5.2/3)
1288
1289(ipt:parse/print-test
1290"RDATE;VALUE=DATE:19970101,19970120,19970217,19970421,19970526,19970704,19970901,19971014,19971128,19971129,19971225\n"
1291:parser icalendar-parse-property
1292:printer icalendar-print-property-node
1293:source rfc5545-sec3.8.5.2/4)
1294
1295(ipt:parse/print-test
1296"RRULE:FREQ=DAILY;COUNT=10\n"
1297:parser icalendar-parse-property
1298:printer icalendar-print-property-node
1299:source rfc5545-sec3.8.5.3/1)
1300
1301(ipt:parse/print-test
1302"RRULE:FREQ=DAILY;UNTIL=19971224T000000Z\n"
1303:parser icalendar-parse-property
1304:printer icalendar-print-property-node
1305:source rfc5545-sec3.8.5.3/2)
1306
1307(ipt:parse/print-test
1308"RRULE:FREQ=DAILY;INTERVAL=2\n"
1309:parser icalendar-parse-property
1310:printer icalendar-print-property-node
1311:source rfc5545-sec3.8.5.3/3)
1312
1313(ipt:parse/print-test
1314"RRULE:FREQ=DAILY;INTERVAL=10;COUNT=5\n"
1315:parser icalendar-parse-property
1316:printer icalendar-print-property-node
1317:source rfc5545-sec3.8.5.3/4)
1318
1319(ipt:parse/print-test
1320"RRULE:FREQ=YEARLY;UNTIL=20000131T140000Z;BYMONTH=1;BYDAY=SU,MO,TU,WE,TH,FR,SA\n"
1321:parser icalendar-parse-property
1322:printer icalendar-print-property-node
1323:source rfc5545-sec3.8.5.3/5)
1324
1325(ipt:parse/print-test
1326"RRULE:FREQ=DAILY;UNTIL=20000131T140000Z;BYMONTH=1\n"
1327:parser icalendar-parse-property
1328:printer icalendar-print-property-node
1329:source rfc5545-sec3.8.5.3/6)
1330
1331(ipt:parse/print-test
1332"RRULE:FREQ=WEEKLY;COUNT=10\n"
1333:parser icalendar-parse-property
1334:printer icalendar-print-property-node
1335:source rfc5545-sec3.8.5.3/7)
1336
1337(ipt:parse/print-test
1338"RRULE:FREQ=WEEKLY;UNTIL=19971224T000000Z\n"
1339:parser icalendar-parse-property
1340:printer icalendar-print-property-node
1341:source rfc5545-sec3.8.5.3/8)
1342
1343(ipt:parse/print-test
1344"RRULE:FREQ=WEEKLY;INTERVAL=2;WKST=SU\n"
1345:parser icalendar-parse-property
1346:printer icalendar-print-property-node
1347:source rfc5545-sec3.8.5.3/9)
1348
1349(ipt:parse/print-test
1350"RRULE:FREQ=WEEKLY;UNTIL=19971007T000000Z;WKST=SU;BYDAY=TU,TH\n"
1351:parser icalendar-parse-property
1352:printer icalendar-print-property-node
1353:source rfc5545-sec3.8.5.3/10)
1354
1355(ipt:parse/print-test
1356"RRULE:FREQ=WEEKLY;COUNT=10;WKST=SU;BYDAY=TU,TH\n"
1357:parser icalendar-parse-property
1358:printer icalendar-print-property-node
1359:source rfc5545-sec3.8.5.3/11)
1360
1361(ipt:parse/print-test
1362"RRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR\n"
1363:parser icalendar-parse-property
1364:printer icalendar-print-property-node
1365:source rfc5545-sec3.8.5.3/12)
1366
1367(ipt:parse/print-test
1368"RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=8;WKST=SU;BYDAY=TU,TH\n"
1369:parser icalendar-parse-property
1370:printer icalendar-print-property-node
1371:source rfc5545-sec3.8.5.3/13)
1372
1373(ipt:parse/print-test
1374"RRULE:FREQ=MONTHLY;COUNT=10;BYDAY=1FR\n"
1375:parser icalendar-parse-property
1376:printer icalendar-print-property-node
1377:source rfc5545-sec3.8.5.3/14)
1378
1379(ipt:parse/print-test
1380"RRULE:FREQ=MONTHLY;UNTIL=19971224T000000Z;BYDAY=1FR\n"
1381:parser icalendar-parse-property
1382:printer icalendar-print-property-node
1383:source rfc5545-sec3.8.5.3/15)
1384
1385(ipt:parse/print-test
1386"RRULE:FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=1SU,-1SU\n"
1387:parser icalendar-parse-property
1388:printer icalendar-print-property-node
1389:source rfc5545-sec3.8.5.3/16)
1390
1391(ipt:parse/print-test
1392"RRULE:FREQ=MONTHLY;COUNT=6;BYDAY=-2MO\n"
1393:parser icalendar-parse-property
1394:printer icalendar-print-property-node
1395:source rfc5545-sec3.8.5.3/17)
1396
1397(ipt:parse/print-test
1398"RRULE:FREQ=MONTHLY;BYMONTHDAY=-3\n"
1399:parser icalendar-parse-property
1400:printer icalendar-print-property-node
1401:source rfc5545-sec3.8.5.3/18)
1402
1403(ipt:parse/print-test
1404"RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=2,15\n"
1405:parser icalendar-parse-property
1406:printer icalendar-print-property-node
1407:source rfc5545-sec3.8.5.3/19)
1408
1409(ipt:parse/print-test
1410"RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=1,-1\n"
1411:parser icalendar-parse-property
1412:printer icalendar-print-property-node
1413:source rfc5545-sec3.8.5.3/20)
1414
1415(ipt:parse/print-test
1416"RRULE:FREQ=MONTHLY;INTERVAL=18;COUNT=10;BYMONTHDAY=10,11,12,13,14,15\n"
1417:parser icalendar-parse-property
1418:printer icalendar-print-property-node
1419:source rfc5545-sec3.8.5.3/21)
1420
1421(ipt:parse/print-test
1422"RRULE:FREQ=MONTHLY;INTERVAL=2;BYDAY=TU\n"
1423:parser icalendar-parse-property
1424:printer icalendar-print-property-node
1425:source rfc5545-sec3.8.5.3/22)
1426
1427(ipt:parse/print-test
1428"RRULE:FREQ=YEARLY;COUNT=10;BYMONTH=6,7\n"
1429:parser icalendar-parse-property
1430:printer icalendar-print-property-node
1431:source rfc5545-sec3.8.5.3/23)
1432
1433(ipt:parse/print-test
1434"RRULE:FREQ=YEARLY;INTERVAL=2;COUNT=10;BYMONTH=1,2,3\n"
1435:parser icalendar-parse-property
1436:printer icalendar-print-property-node
1437:source rfc5545-sec3.8.5.3/24)
1438
1439(ipt:parse/print-test
1440"RRULE:FREQ=YEARLY;INTERVAL=3;COUNT=10;BYYEARDAY=1,100,200\n"
1441:parser icalendar-parse-property
1442:printer icalendar-print-property-node
1443:source rfc5545-sec3.8.5.3/25)
1444
1445(ipt:parse/print-test
1446"RRULE:FREQ=YEARLY;BYDAY=20MO\n"
1447:parser icalendar-parse-property
1448:printer icalendar-print-property-node
1449:source rfc5545-sec3.8.5.3/26)
1450
1451(ipt:parse/print-test
1452"RRULE:FREQ=YEARLY;BYWEEKNO=20;BYDAY=MO\n"
1453:parser icalendar-parse-property
1454:printer icalendar-print-property-node
1455:source rfc5545-sec3.8.5.3/27)
1456
1457(ipt:parse/print-test
1458"RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=TH\n"
1459:parser icalendar-parse-property
1460:printer icalendar-print-property-node
1461:source rfc5545-sec3.8.5.3/28)
1462
1463(ipt:parse/print-test
1464"RRULE:FREQ=YEARLY;BYDAY=TH;BYMONTH=6,7,8\n"
1465:parser icalendar-parse-property
1466:printer icalendar-print-property-node
1467:source rfc5545-sec3.8.5.3/29)
1468
1469(ipt:parse/print-test
1470"RRULE:FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13\n"
1471:parser icalendar-parse-property
1472:printer icalendar-print-property-node
1473:source rfc5545-sec3.8.5.3/30)
1474
1475(ipt:parse/print-test
1476"RRULE:FREQ=MONTHLY;BYDAY=SA;BYMONTHDAY=7,8,9,10,11,12,13\n"
1477:parser icalendar-parse-property
1478:printer icalendar-print-property-node
1479:source rfc5545-sec3.8.5.3/31)
1480
1481(ipt:parse/print-test
1482"RRULE:FREQ=YEARLY;INTERVAL=4;BYMONTH=11;BYDAY=TU;BYMONTHDAY=2,3,4,5,6,7,8\n"
1483:parser icalendar-parse-property
1484:printer icalendar-print-property-node
1485:source rfc5545-sec3.8.5.3/32)
1486
1487(ipt:parse/print-test
1488"RRULE:FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=3\n"
1489:parser icalendar-parse-property
1490:printer icalendar-print-property-node
1491:source rfc5545-sec3.8.5.3/33)
1492
1493(ipt:parse/print-test
1494"RRULE:FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-2\n"
1495:parser icalendar-parse-property
1496:printer icalendar-print-property-node
1497:source rfc5545-sec3.8.5.3/34)
1498
1499(ipt:parse/print-test
1500"RRULE:FREQ=HOURLY;INTERVAL=3;UNTIL=19970902T170000Z\n"
1501:parser icalendar-parse-property
1502:printer icalendar-print-property-node
1503:source rfc5545-sec3.8.5.3/35)
1504
1505(ipt:parse/print-test
1506"RRULE:FREQ=MINUTELY;INTERVAL=15;COUNT=6\n"
1507:parser icalendar-parse-property
1508:printer icalendar-print-property-node
1509:source rfc5545-sec3.8.5.3/36)
1510
1511(ipt:parse/print-test
1512"RRULE:FREQ=MINUTELY;INTERVAL=90;COUNT=4\n"
1513:parser icalendar-parse-property
1514:printer icalendar-print-property-node
1515:source rfc5545-sec3.8.5.3/37)
1516
1517(ipt:parse/print-test
1518"RRULE:FREQ=DAILY;BYHOUR=9,10,11,12,13,14,15,16;BYMINUTE=0,20,40\n"
1519:parser icalendar-parse-property
1520:printer icalendar-print-property-node
1521:source rfc5545-sec3.8.5.3/38)
1522
1523(ipt:parse/print-test
1524"RRULE:FREQ=MINUTELY;INTERVAL=20;BYHOUR=9,10,11,12,13,14,15,16\n"
1525:parser icalendar-parse-property
1526:printer icalendar-print-property-node
1527:source rfc5545-sec3.8.5.3/39)
1528
1529(ipt:parse/print-test
1530"RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=MO\n"
1531:parser icalendar-parse-property
1532:printer icalendar-print-property-node
1533:source rfc5545-sec3.8.5.3/40)
1534
1535(ipt:parse/print-test
1536"RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=SU\n"
1537:parser icalendar-parse-property
1538:printer icalendar-print-property-node
1539:source rfc5545-sec3.8.5.3/41)
1540
1541(ipt:parse/print-test
1542"RRULE:FREQ=MONTHLY;BYMONTHDAY=15,30;COUNT=5\n"
1543:parser icalendar-parse-property
1544:printer icalendar-print-property-node
1545:source rfc5545-sec3.8.5.3/42)
1546
1547(ipt:parse/print-test
1548"ACTION:AUDIO\n"
1549:parser icalendar-parse-property
1550:printer icalendar-print-property-node
1551:source rfc5545-sec3.8.6.1/1)
1552
1553(ipt:parse/print-test
1554"ACTION:DISPLAY\n"
1555:parser icalendar-parse-property
1556:printer icalendar-print-property-node
1557:source rfc5545-sec3.8.6.1/2)
1558
1559(ipt:parse/print-test
1560"REPEAT:4\n"
1561:parser icalendar-parse-property
1562:printer icalendar-print-property-node
1563:source rfc5545-sec3.8.6.2/1)
1564
1565(ipt:parse/print-test
1566"TRIGGER:-PT15M\n"
1567:parser icalendar-parse-property
1568:printer icalendar-print-property-node
1569:source rfc5545-sec3.8.6.3/1)
1570
1571(ipt:parse/print-test
1572"TRIGGER;RELATED=END:PT5M\n"
1573:parser icalendar-parse-property
1574:printer icalendar-print-property-node
1575:source rfc5545-sec3.8.6.3/2)
1576
1577(ipt:parse/print-test
1578"TRIGGER;VALUE=DATE-TIME:19980101T050000Z\n"
1579:parser icalendar-parse-property
1580:printer icalendar-print-property-node
1581:source rfc5545-sec3.8.6.3/3)
1582
1583(ipt:parse/print-test
1584"CREATED:19960329T133000Z\n"
1585:parser icalendar-parse-property
1586:printer icalendar-print-property-node
1587:source rfc5545-sec3.8.7.1/1)
1588
1589(ipt:parse/print-test
1590"DTSTAMP:19971210T080000Z\n"
1591:parser icalendar-parse-property
1592:printer icalendar-print-property-node
1593:source rfc5545-sec3.8.7.2/1)
1594
1595(ipt:parse/print-test
1596"LAST-MODIFIED:19960817T133000Z\n"
1597:parser icalendar-parse-property
1598:printer icalendar-print-property-node
1599:source rfc5545-sec3.8.7.3/1)
1600
1601(ipt:parse/print-test
1602"SEQUENCE:0\n"
1603:parser icalendar-parse-property
1604:printer icalendar-print-property-node
1605:source rfc5545-sec3.8.7.4/1)
1606
1607(ipt:parse/print-test
1608"SEQUENCE:2\n"
1609:parser icalendar-parse-property
1610:printer icalendar-print-property-node
1611:source rfc5545-sec3.8.7.4/2)
1612
1613(ipt:parse/print-test
1614"DRESSCODE:CASUAL\n"
1615:parser icalendar-parse-property
1616:printer icalendar-print-property-node
1617:source rfc5545-sec3.8.8.1/1)
1618
1619(ipt:parse/print-test
1620"NON-SMOKING;VALUE=BOOLEAN:TRUE\n"
1621:parser icalendar-parse-property
1622:printer icalendar-print-property-node
1623:source rfc5545-sec3.8.8.1/2)
1624
1625(ipt:parse/print-test
1626"X-ABC-MMSUBJ;VALUE=URI;FMTTYPE=audio/basic:http://www.example.org/mysubj.au\n"
1627:parser icalendar-parse-property
1628:printer icalendar-print-property-node
1629:source rfc5545-sec3.8.8.2/1)
1630
1631(ipt:parse/print-test
1632"REQUEST-STATUS:2.0;Success\n"
1633:parser icalendar-parse-property
1634:printer icalendar-print-property-node
1635:source rfc5545-sec3.8.8.3/1)
1636
1637(ipt:parse/print-test
1638"REQUEST-STATUS:3.1;Invalid property value;DTSTART:96-Apr-01\n"
1639:parser icalendar-parse-property
1640:printer icalendar-print-property-node
1641:source rfc5545-sec3.8.8.3/2)
1642
1643(ipt:parse/print-test
1644"REQUEST-STATUS:2.8; Success\\, repeating event ignored. Scheduled as a single event.;RRULE:FREQ=WEEKLY\\;INTERVAL=2\n"
1645:parser icalendar-parse-property
1646:printer icalendar-print-property-node
1647:source rfc5545-sec3.8.8.3/3)
1648
1649(ipt:parse/print-test
1650"REQUEST-STATUS:4.1;Event conflict. Date-time is busy.\n"
1651:parser icalendar-parse-property
1652:printer icalendar-print-property-node
1653:source rfc5545-sec3.8.8.3/4)
1654
1655(ipt:parse/print-test
1656"REQUEST-STATUS:3.7;Invalid calendar user;ATTENDEE:mailto:jsmith@example.com\n"
1657:parser icalendar-parse-property
1658:printer icalendar-print-property-node
1659:source rfc5545-sec3.8.8.3/5)
1660
1661(ipt:parse/print-test
1662"BEGIN:VCALENDAR
1663PRODID:-//xyz Corp//NONSGML PDA Calendar Version 1.0//EN
1664VERSION:2.0
1665BEGIN:VEVENT
1666DTSTAMP:19960704T120000Z
1667UID:uid1@example.com
1668ORGANIZER:mailto:jsmith@example.com
1669DTSTART:19960918T143000Z
1670DTEND:19960920T220000Z
1671STATUS:CONFIRMED
1672CATEGORIES:CONFERENCE
1673SUMMARY:Networld+Interop Conference
1674DESCRIPTION:Networld+Interop Conference and Exhibit\\nAtlanta World Congress Center\\nAtlanta\\, Georgia
1675END:VEVENT
1676END:VCALENDAR
1677"
1678:parser icalendar-parse-calendar
1679:printer icalendar-print-calendar-node
1680:source rfc5545-sec4/1)
1681
1682(ipt:parse/print-test
1683"BEGIN:VCALENDAR
1684PRODID:-//RDU Software//NONSGML HandCal//EN
1685VERSION:2.0
1686BEGIN:VTIMEZONE
1687TZID:America/New_York
1688BEGIN:STANDARD
1689DTSTART:19981025T020000
1690TZOFFSETFROM:-0400
1691TZOFFSETTO:-0500
1692TZNAME:EST
1693END:STANDARD
1694BEGIN:DAYLIGHT
1695DTSTART:19990404T020000
1696TZOFFSETFROM:-0500
1697TZOFFSETTO:-0400
1698TZNAME:EDT
1699END:DAYLIGHT
1700END:VTIMEZONE
1701BEGIN:VEVENT
1702DTSTAMP:19980309T231000Z
1703UID:guid-1.example.com
1704ORGANIZER:mailto:mrbig@example.com
1705ATTENDEE;RSVP=TRUE;ROLE=REQ-PARTICIPANT;CUTYPE=GROUP:mailto:employee-A@example.com
1706DESCRIPTION:Project XYZ Review Meeting
1707CATEGORIES:MEETING
1708CLASS:PUBLIC
1709CREATED:19980309T130000Z
1710SUMMARY:XYZ Project Review
1711DTSTART;TZID=America/New_York:19980312T083000
1712DTEND;TZID=America/New_York:19980312T093000
1713LOCATION:1CP Conference Room 4350
1714END:VEVENT
1715END:VCALENDAR
1716"
1717:parser icalendar-parse-calendar
1718:printer icalendar-print-calendar-node
1719:source rfc5545-sec4/2)
1720
1721(ipt:parse/print-test
1722"BEGIN:VCALENDAR
1723METHOD:xyz
1724VERSION:2.0
1725PRODID:-//ABC Corporation//NONSGML My Product//EN
1726BEGIN:VEVENT
1727DTSTAMP:19970324T120000Z
1728SEQUENCE:0
1729UID:uid3@example.com
1730ORGANIZER:mailto:jdoe@example.com
1731ATTENDEE;RSVP=TRUE:mailto:jsmith@example.com
1732DTSTART:19970324T123000Z
1733DTEND:19970324T210000Z
1734CATEGORIES:MEETING,PROJECT
1735CLASS:PUBLIC
1736SUMMARY:Calendaring Interoperability Planning Meeting
1737DESCRIPTION:Discuss how we can test c&s interoperability\\nusing iCalendar and other IETF standards.
1738LOCATION:LDB Lobby
1739ATTACH;FMTTYPE=application/postscript:ftp://example.com/pub/conf/bkgrnd.ps
1740END:VEVENT
1741END:VCALENDAR
1742"
1743:parser icalendar-parse-calendar
1744:printer icalendar-print-calendar-node
1745:source rfc5545-sec4/3)
1746
1747(ipt:parse/print-test
1748;; Corrected. The TRIGGER property originally did not specify
1749;; VALUE=DATE-TIME, which is required since it is not the default type.
1750;; See https://www.rfc-editor.org/errata/eid2039
1751"BEGIN:VCALENDAR
1752VERSION:2.0
1753PRODID:-//ABC Corporation//NONSGML My Product//EN
1754BEGIN:VTODO
1755DTSTAMP:19980130T134500Z
1756SEQUENCE:2
1757UID:uid4@example.com
1758ORGANIZER:mailto:unclesam@example.com
1759ATTENDEE;PARTSTAT=ACCEPTED:mailto:jqpublic@example.com
1760DUE:19980415T000000
1761STATUS:NEEDS-ACTION
1762SUMMARY:Submit Income Taxes
1763BEGIN:VALARM
1764ACTION:AUDIO
1765TRIGGER;VALUE=DATE-TIME:19980403T120000Z
1766ATTACH;FMTTYPE=audio/basic:http://example.com/pub/audio-files/ssbanner.aud
1767REPEAT:4
1768DURATION:PT1H
1769END:VALARM
1770END:VTODO
1771END:VCALENDAR
1772"
1773:parser icalendar-parse-calendar
1774:printer icalendar-print-calendar-node
1775:source rfc5545-sec4/4)
1776
1777(ipt:parse/print-test
1778"BEGIN:VCALENDAR
1779VERSION:2.0
1780PRODID:-//ABC Corporation//NONSGML My Product//EN
1781BEGIN:VJOURNAL
1782DTSTAMP:19970324T120000Z
1783UID:uid5@example.com
1784ORGANIZER:mailto:jsmith@example.com
1785STATUS:DRAFT
1786CLASS:PUBLIC
1787CATEGORIES:Project Report,XYZ,Weekly Meeting
1788DESCRIPTION:Project xyz Review Meeting Minutes\\nAgenda\\n1. Review of project version 1.0 requirements.\\n2.Definitionof project processes.\\n3. Review of project schedule.\\nParticipants: John Smith\\, Jane Doe\\, Jim Dandy\\n-It was decided that the requirements need to be signed off byproduct marketing.\\n-P roject processes were accepted.\\n-Project schedule needs to account for scheduled holidaysand employee vacation time. Check with HR for specificdates.\\n-New schedule will be distributed by Friday.\\n-Next weeks meeting is cancelled. No meeting until 3/23.
1789END:VJOURNAL
1790END:VCALENDAR
1791"
1792:parser icalendar-parse-calendar
1793:printer icalendar-print-calendar-node
1794:source rfc5545-sec4/5)
1795
1796(ipt:parse/print-test
1797;; Corrected. Original text in the standard is missing UID and DTSTAMP.
1798;; See https://www.rfc-editor.org/errata/eid4149
1799"BEGIN:VCALENDAR
1800VERSION:2.0
1801PRODID:-//RDU Software//NONSGML HandCal//EN
1802BEGIN:VFREEBUSY
1803UID:19970901T115957Z-76A912@example.com
1804DTSTAMP:19970901T120000Z
1805ORGANIZER:mailto:jsmith@example.com
1806DTSTART:19980313T141711Z
1807DTEND:19980410T141711Z
1808FREEBUSY:19980314T233000Z/19980315T003000Z
1809FREEBUSY:19980316T153000Z/19980316T163000Z
1810FREEBUSY:19980318T030000Z/19980318T040000Z
1811URL:http://www.example.com/calendar/busytime/jsmith.ifb
1812END:VFREEBUSY
1813END:VCALENDAR
1814"
1815:parser icalendar-parse-calendar
1816:printer icalendar-print-calendar-node
1817:source rfc5545-sec4/6)
1818
1819
1820;; Tests from real world data:
1821(ert-deftest ipt:bad-organizer-params ()
1822 "Real example: bad ORGANIZER property with params introduced by colon"
1823 (let ((bad "ORGANIZER:CN=ORGANIZER:mailto:anonymized@domain.example\n")
1824 (ok "ORGANIZER;CN=ORGANIZER:mailto:anonymized@domain.example\n"))
1825 (should-error (ical:parse-from-string 'ical:organizer bad))
1826 (should (ical:ast-node-p (ical:parse-from-string 'ical:organizer ok)))))
1827
1828(ert-deftest ipt:bad-attendee ()
1829 "Real example: bad ATTENDEE property missing mailto: prefix"
1830 (let ((bad "ATTENDEE;ROLE=REQ-PARTICIPANT;CN=TRAVELLER:anonymized@domain.example\n")
1831 (ok "ATTENDEE;ROLE=REQ-PARTICIPANT;CN=TRAVELLER:mailto:anonymized@domain.example\n"))
1832 (should-error (ical:parse-from-string 'ical:attendee bad))
1833 (should (ical:ast-node-p (ical:parse-from-string 'ical:attendee ok)))))
1834
1835(ert-deftest ipt:bad-attach ()
1836 "Real example: bad ATTACH property containing broken URI"
1837 (let ((bad "ATTACH;VALUE=URI:Glass\n")
1838 (ok "ATTACH;VALUE=URI:https://example.com\n"))
1839 (should-error (ical:parse-from-string 'ical:attach bad))
1840 (should (ical:ast-node-p (ical:parse-from-string 'ical:attach ok)))))
1841
1842(ert-deftest ipt:bad-cnparam ()
1843 "Real example: bad unquoted CN parameter containing a comma"
1844 (let ((bad "ORGANIZER;CN=Hartlauer Geschäft Wien, Taborstr. 18:mailto:anonymized@domain.example\n")
1845 (ok "ORGANIZER;CN=\"Hartlauer Geschäft Wien, Taborstr. 18\":mailto:anonymized@domain.example\n"))
1846 ;; strict parser should reject bad but accept ok:
1847 (let ((ical:parse-strictly t))
1848 (should (ical:ast-node-p (ical:parse-from-string 'ical:organizer ok)))
1849 (should-error (ical:parse-from-string 'ical:organizer bad)))
1850 ;; relaxed parser should accept bad:
1851 (let ((ical:parse-strictly nil))
1852 (should (ical:ast-node-p (ical:parse-from-string 'ical:organizer bad))))))
1853
1854(ert-deftest ipt:fix-bad-description ()
1855 "Real example: bad DESCRIPTION property containing blank lines,
1856fixed by `icalendar-fix-blank-lines'."
1857 (let ((bad "BEGIN:VCALENDAR
1858VERSION:2.0
1859CALSCALE:GREGORIAN
1860METHOD:REQUEST
1861BEGIN:VEVENT
1862UID:45dd7698-5c53-47e3-9280-19c5dff62571
1863PRIORITY:1
1864DTSTART:20210721T175200
1865DTEND:20210721T192400
1866LOCATION:Verona Porta Nuova
1867DESCRIPTION:Verona Porta Nuova-Firenze S. M. Novella;Train: Frecciarossa 8527, departing from Verona Porta Nuova Hours: 17:52; arriving at Firenze S. M. Novella Hours: 19:24 Coach 8, Position 7A; pnr code CLS345
1868
1869
1870SUMMARY:Trip Verona Porta Nuova-Firenze S. M. Novella, Train Frecciarossa 8527, Coach 8, Position 7A, PNR CLS345,
1871ORGANIZER;CN=ORGANIZER:mailto:anonymized@domain.example
1872ATTENDEE;ROLE=REQ-PARTICIPANT;CN=BUYER:mailto:anonymized@domain.example
1873ATTENDEE;ROLE=REQ-PARTICIPANT;CN=TRAVELLER:mailto:anonymized@domain.example
1874END:VEVENT
1875END:VCALENDAR
1876"))
1877 ;; The default parser should produce an error on the blank lines in
1878 ;; DESCRIPTION:
1879 (let ((ical:pre-parsing-hook nil))
1880 (with-temp-buffer
1881 (ical:init-error-buffer)
1882 (insert bad)
1883 (goto-char (point-min))
1884 (ical:parse)
1885 ;; Parsing should produce error at the bad description property:
1886 (should (ical:errors-p))))
1887 ;; cleaning up the blank lines before parsing should correct this:
1888 (let ((ical:pre-parsing-hook '(ical:fix-blank-lines)))
1889 (with-temp-buffer
1890 (ical:init-error-buffer)
1891 (insert bad)
1892 (goto-char (point-min))
1893 (let ((vcal (ical:parse)))
1894 (should (not (ical:errors-p)))
1895 (ical:with-component vcal
1896 ((ical:vevent vevent))
1897 (ical:with-component vevent
1898 ((ical:description :value description))
1899 (let* ((expected "CLS345")
1900 (end (length description))
1901 (start (- end (length expected))))
1902 (should (equal expected
1903 (substring description start end)))))))))))
1904
1905(ert-deftest ipt:bad-hyphenated-dates ()
1906 "Real example: bad date values containing hyphens, fixed by
1907`icalendar-fix-hyphenated-dates'."
1908 (let ((bad "BEGIN:VCALENDAR
1909X-LOTUS-CHARSET:UTF-8
1910VERSION:2.0
1911PRODID:http://www.bahn.de
1912METHOD:PUBLISH
1913BEGIN:VTIMEZONE
1914TZID:Europe/Berlin
1915X-LIC-LOCATION:Europe/Berlin
1916BEGIN:DAYLIGHT
1917TZOFFSETFROM:+0100
1918TZOFFSETTO:+0200
1919TZNAME:CEST
1920DTSTART:19700329T020000
1921RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3
1922END:DAYLIGHT
1923BEGIN:STANDARD
1924TZOFFSETFROM:+0200
1925TZOFFSETTO:+0100
1926TZNAME:CET
1927DTSTART:19701025T030000
1928RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10
1929END:STANDARD
1930END:VTIMEZONE
1931BEGIN:VEVENT
1932UID:bahn2023-08-29141400
1933CLASS:PUBLIC
1934SUMMARY:Frankfurt(Main)Hbf -> Hamburg Hbf
1935DTSTART;TZID=Europe/Berlin:2023-08-29T141400
1936DTEND;TZID=Europe/Berlin:2023-08-29T183600
1937DTSTAMP:2023-07-30T194700Z
1938END:VEVENT
1939END:VCALENDAR
1940"))
1941 ;; default parser should skip the invalid DTSTART, DTEND, and DTSTAMP values:
1942 (let ((ical:pre-parsing-hook nil))
1943 (with-temp-buffer
1944 (ical:init-error-buffer)
1945 (insert bad)
1946 (goto-char (point-min))
1947 (let ((vcal (ical:parse)))
1948 ;; Parsing should produce errors as the bad properties are
1949 ;; skipped:
1950 (should (ical:errors-p))
1951 ;; The resulting calendar is invalid because the VEVENT
1952 ;; contains no DTSTAMP:
1953 (should-error (ical:ast-node-valid-p vcal t)))))
1954 ;; cleaning up the hyphenated dates before parsing should correct
1955 ;; these problems:
1956 (let ((ical:pre-parsing-hook '(ical:fix-hyphenated-dates)))
1957 (with-temp-buffer
1958 (ical:init-error-buffer)
1959 (insert bad)
1960 (goto-char (point-min))
1961 (let ((vcal (ical:parse))
1962 (expected-dtstamp
1963 (ical:make-date-time :year 2023 :month 7 :day 30
1964 :hour 19 :minute 47 :second 0
1965 :zone 0)))
1966 (should (not (ical:errors-p)))
1967 (should (ical:ast-node-valid-p vcal t))
1968 (ical:with-component vcal
1969 ((ical:vevent vevent))
1970 (ical:with-component vevent
1971 ((ical:dtstamp :value dtstamp))
1972 (should (equal dtstamp expected-dtstamp)))))))))
1973
1974(ert-deftest ipt:bad-user-addresses ()
1975 "Real example: bad calendar user addresses missing \"mailto:\", fixed by
1976`icalendar-fix-missing-mailtos'."
1977 (let ((bad "BEGIN:VCALENDAR
1978VERSION:2.0
1979PRODID:missing
1980CALSCALE:GREGORIAN
1981METHOD:REQUEST
1982BEGIN:VEVENT
1983UID:45dd7698-5c53-47e3-9280-19c5dff62571
1984PRIORITY:1
1985DTSTART:20210721T175200
1986DTEND:20210721T192400
1987LOCATION:Verona Porta Nuova
1988SUMMARY:Trip Verona Porta Nuova-Firenze S. M. Novella
1989ORGANIZER;SENT-BY=\"other@domain.example\":anonymized@domain.example
1990ATTENDEE;ROLE=REQ-PARTICIPANT;CN=TRAVELER:traveler@domain.example
1991END:VEVENT
1992END:VCALENDAR
1993"))
1994 (let ((ical:pre-parsing-hook nil))
1995 (with-temp-buffer
1996 (ical:init-error-buffer)
1997 (insert bad)
1998 (goto-char (point-min))
1999 (ical:parse)
2000 ;; Parsing should produce errors as the bad properties are
2001 ;; skipped:
2002 (should (ical:errors-p))))
2003 ;; cleaning up the addresses before parsing should correct
2004 ;; these problems:
2005 (let ((ical:pre-parsing-hook '(ical:fix-missing-mailtos)))
2006 (with-temp-buffer
2007 (ical:init-error-buffer)
2008 (insert bad)
2009 (goto-char (point-min))
2010 (let ((vcal (ical:parse))
2011 (expected-attendee "mailto:traveler@domain.example")
2012 (expected-organizer "mailto:anonymized@domain.example")
2013 (expected-sender "mailto:other@domain.example"))
2014 (should (not (ical:errors-p)))
2015 (ical:with-component vcal
2016 ((ical:vevent vevent))
2017 (ical:with-component vevent
2018 ((ical:attendee :value attendee)
2019 (ical:organizer :value organizer))
2020 (should (equal attendee expected-attendee))
2021 (should (equal organizer expected-organizer))
2022 (ical:with-property organizer
2023 ((ical:sentbyparam :value sent-by))
2024 (should (equal sent-by expected-sender))))))))))
2025
2026
2027
2028
2029;; Local Variables:
2030;; read-symbol-shorthands: (("ipt:" . "icalendar-parser-test-") ("ical:" . "icalendar-"))
2031;; End:
2032;;; icalendar-parser-tests.el ends here
diff --git a/test/lisp/calendar/icalendar-recur-tests.el b/test/lisp/calendar/icalendar-recur-tests.el
new file mode 100644
index 00000000000..1df2c5b17e5
--- /dev/null
+++ b/test/lisp/calendar/icalendar-recur-tests.el
@@ -0,0 +1,2880 @@
1;;; icalendar-recur-tests.el --- Tests for icalendar-recur -*- lexical-binding: t; -*-
2;; Copyright (C) 2025 Free Software Foundation, Inc.
3
4;; This file is part of GNU Emacs.
5
6;; GNU Emacs is free software: you can redistribute it and/or modify
7;; it under the terms of the GNU General Public License as published by
8;; the Free Software Foundation, either version 3 of the License, or
9;; (at your option) any later version.
10
11;; GNU Emacs is distributed in the hope that it will be useful,
12;; but WITHOUT ANY WARRANTY; without even the implied warranty of
13;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14;; GNU General Public License for more details.
15
16;; You should have received a copy of the GNU General Public License
17;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
18
19;;; Code:
20
21(eval-when-compile (require 'cl-lib))
22(require 'ert)
23(eval-when-compile (require 'icalendar-macs))
24(require 'icalendar-recur)
25(require 'icalendar-utils)
26(require 'icalendar-parser)
27(require 'icalendar-ast)
28
29;; Some constants for tests that use time zones:
30(defconst ict:tz-eastern
31 (ical:parse-from-string 'ical:vtimezone
32"BEGIN:VTIMEZONE
33TZID:America/New_York
34LAST-MODIFIED:20050809T050000Z
35BEGIN:DAYLIGHT
36DTSTART:19670430T020000
37RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=-1SU;UNTIL=19730429T070000Z
38TZOFFSETFROM:-0500
39TZOFFSETTO:-0400
40TZNAME:EDT
41END:DAYLIGHT
42BEGIN:STANDARD
43DTSTART:19671029T020000
44RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=20061029T060000Z
45TZOFFSETFROM:-0400
46TZOFFSETTO:-0500
47TZNAME:EST
48END:STANDARD
49BEGIN:DAYLIGHT
50DTSTART:19740106T020000
51RDATE:19750223T020000
52TZOFFSETFROM:-0500
53TZOFFSETTO:-0400
54TZNAME:EDT
55END:DAYLIGHT
56BEGIN:DAYLIGHT
57DTSTART:19760425T020000
58RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=-1SU;UNTIL=19860427T070000Z
59TZOFFSETFROM:-0500
60TZOFFSETTO:-0400
61TZNAME:EDT
62END:DAYLIGHT
63BEGIN:DAYLIGHT
64DTSTART:19870405T020000
65RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU;UNTIL=20060402T070000Z
66TZOFFSETFROM:-0500
67TZOFFSETTO:-0400
68TZNAME:EDT
69END:DAYLIGHT
70BEGIN:DAYLIGHT
71DTSTART:20070311T020000
72RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
73TZOFFSETFROM:-0500
74TZOFFSETTO:-0400
75TZNAME:EDT
76END:DAYLIGHT
77BEGIN:STANDARD
78DTSTART:20071104T020000
79RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
80TZOFFSETFROM:-0400
81TZOFFSETTO:-0500
82TZNAME:EST
83END:STANDARD
84END:VTIMEZONE
85")
86"`icalendar-vtimezone' representing America/New_York (Eastern) time.")
87
88(defconst ict:est-latest
89 (ical:with-component ict:tz-eastern
90 ((ical:standard :all stds))
91 (seq-find (lambda (obs)
92 (ical:date-time=
93 (ical:make-date-time :year 2007 :month 11 :day 4
94 :hour 2 :minute 0 :second 0)
95 (ical:with-property-of obs 'ical:dtstart nil value)))
96 stds))
97 "The observance of Eastern Standard Time which began 2007-11-04")
98
99(defconst ict:edt-latest
100 (ical:with-component ict:tz-eastern
101 ((ical:daylight :all dls))
102 (seq-find (lambda (obs)
103 (ical:date-time=
104 (ical:make-date-time :year 2007 :month 3 :day 11
105 :hour 2 :minute 0 :second 0)
106 (ical:with-property-of obs 'ical:dtstart nil value)))
107 dls))
108 "The observance of Eastern Daylight Time which began 2007-03-11")
109
110(defconst ict:est -18000 ;; = -0500
111 "UTC offset for Eastern Standard Time")
112
113(defconst ict:edt -14400 ;; = -0400
114 "UTC offset for Eastern Daylight Time")
115
116
117;; Tests for basic functions:
118
119(ert-deftest ict:recur-bysetpos-filter ()
120 "Test that `icr:make-bysetpos-filter' filters correctly by position"
121 (let* ((t1 (list 1 1 2024))
122 (t2 (list 2 1 2024))
123 (t3 (list 12 30 2024))
124 (dts (list t1 t2 t3))
125 (filter (icr:make-bysetpos-filter (list 1 -1)))
126 (filtered (funcall filter dts)))
127 (should (member t1 filtered))
128 (should (member t3 filtered))
129 (should-not (member t2 filtered))))
130
131(ert-deftest ict:recur-yearday-number ()
132 "Test that `calendar-date-from-day-of-year' finds correct dates"
133 (let* ((year 2025)
134 (daynos (list '(1 . (1 1 2025))
135 '(8 . (1 8 2025))
136 '(-1 . (12 31 2025))
137 '(363 . (12 29 2025)))))
138 (dolist (d daynos)
139 (let ((dayno (car d))
140 (date (cdr d)))
141 (should
142 (equal date (calendar-date-from-day-of-year year dayno)))))))
143
144(ert-deftest ict:date-time-add ()
145 "Does `ical:date-time-add' correctly handle time zone transitions?"
146 ;; A sum that does not use a time zone at all:
147 (let* ((dt (ical:make-date-time :year 2007 :month 1 :day 1
148 :hour 12 :minute 0 :second 0))
149 (delta (make-decoded-time :day 2))
150 (expected (ical:date-time-variant dt :day 3)))
151 (should (equal expected (ical:date-time-add dt delta))))
152
153 ;; A sum that does not cross an observance boundary:
154 (let* ((dt (ical:make-date-time :year 2007 :month 2 :day 1
155 :hour 12 :minute 0 :second 0
156 :zone ict:est :dst nil))
157 (delta (make-decoded-time :day 2))
158 (expected (ical:date-time-variant dt :day 3 :tz 'preserve)))
159 (should (equal expected (ical:date-time-add dt delta ict:tz-eastern))))
160
161 ;; A sum that crosses the Std->DST boundary and should preserve clock time:
162 (let* ((dt (ical:make-date-time :year 2007 :month 3 :day 10
163 :hour 12 :minute 0 :second 0
164 :zone ict:est :dst nil))
165 (delta (make-decoded-time :day 2))
166 (expected (ical:date-time-variant dt :day 12 :zone ict:edt :dst t)))
167 (should (equal expected (ical:date-time-add dt delta ict:tz-eastern))))
168
169 ;; A sum that crosses the Std->DST boundary and should be exactly 48 hours later:
170 (let* ((dt (ical:make-date-time :year 2007 :month 3 :day 10
171 :hour 12 :minute 0 :second 0
172 :zone ict:est :dst nil))
173 (delta (make-decoded-time :hour 48))
174 (expected (ical:date-time-variant dt :day 12 :hour 13
175 :zone ict:edt :dst t)))
176 (should (equal expected (ical:date-time-add dt delta ict:tz-eastern))))
177
178 ;; A sum that crosses the DST->Std boundary and should preserve clock time:
179 (let* ((dt (ical:make-date-time :year 2007 :month 11 :day 3
180 :hour 12 :minute 0 :second 0
181 :zone ict:edt :dst t))
182 (delta (make-decoded-time :day 2))
183 (expected (ical:date-time-variant dt :day 5 :zone ict:est :dst nil)))
184 (should (equal expected (ical:date-time-add dt delta ict:tz-eastern))))
185
186 ;; A sum that crosses the DST->Std boundary and should be exactly 48 hours later:
187 (let* ((dt (ical:make-date-time :year 2007 :month 11 :day 3
188 :hour 12 :minute 0 :second 0
189 :zone ict:edt :dst t))
190 (delta (make-decoded-time :hour 48))
191 (expected (ical:date-time-variant dt :day 5 :hour 11
192 :zone ict:est :dst nil)))
193 (should (equal expected (ical:date-time-add dt delta ict:tz-eastern))))
194
195 ;; A sum that lands exactly on the Std->DST boundary and should result
196 ;; in a clock time one hour later:
197 (let* ((dt (ical:make-date-time :year 2007 :month 3 :day 10
198 :hour 2 :minute 0 :second 0
199 :zone ict:est :dst nil))
200 (delta (make-decoded-time :hour 24))
201 (expected (ical:date-time-variant dt :day 11 :hour 3
202 :zone ict:edt :dst t)))
203 (should (equal expected (ical:date-time-add dt delta ict:tz-eastern))))
204
205 ;; A sum that lands exactly on the DST->Std boundary and should result
206 ;; in a clock time one hour earlier:
207 (let* ((dt (ical:make-date-time :year 2007 :month 11 :day 3
208 :hour 2 :minute 0 :second 0
209 :zone ict:edt :dst t))
210 (delta (make-decoded-time :hour 24))
211 (expected (ical:date-time-variant dt :day 4 :hour 1
212 :zone ict:est :dst nil)))
213 (should (equal expected (ical:date-time-add dt delta ict:tz-eastern)))))
214
215(ert-deftest ict:recur-nonexistent-date-time-p ()
216 "Does `icr:nonexistent-date-time-p' correctly identify nonexistent times?"
217 (let* ((dst-onset (ical:make-date-time :year 2025 :month 3 :day 9
218 :hour 2 :minute 0 :second 0
219 :zone ict:est :dst nil))
220 ;; 2:30 AM falls into the gap when shifting from 2AM EST to 3AM EDT:
221 (nonexistent1 (ical:make-date-time :year 2025 :month 3 :day 9
222 :hour 2 :minute 30 :second 0
223 :zone ict:est :dst nil))
224 (nonexistent2 (ical:date-time-variant nonexistent1
225 :zone ict:edt :dst t))
226 (std-onset (ical:make-date-time :year 2025 :month 11 :day 2
227 :hour 2 :minute 0 :second 0
228 :zone ict:edt :dst t))
229 ;; 1:30AM around the shift back to EST exists twice (once in
230 ;; EDT, once in EST) and should not be nonexistent:
231 (existent1 (ical:make-date-time :year 2025 :month 11 :day 2
232 :hour 1 :minute 30 :second 0
233 :zone ict:edt :dst t))
234 (existent2 (ical:make-date-time :year 2025 :month 11 :day 2
235 :hour 1 :minute 30 :second 0
236 :zone ict:est :dst nil)))
237 (should (icr:nonexistent-date-time-p nonexistent1 dst-onset ict:edt-latest))
238 (should (icr:nonexistent-date-time-p nonexistent2 dst-onset ict:edt-latest))
239 (should-not
240 (icr:nonexistent-date-time-p existent1 std-onset ict:est-latest))
241 (should-not
242 (icr:nonexistent-date-time-p existent2 std-onset ict:est-latest))))
243
244(ert-deftest ict:recur-date-time-occurs-twice-p ()
245 "Does `icr:date-time-occurs-twice-p' correctly identify times that occur twice?"
246 (let* ((std-onset (ical:make-date-time :year 2025 :month 11 :day 2
247 :hour 2 :minute 0 :second 0
248 :zone ict:edt :dst t))
249 ;; 1:00, 1:30 AM occur twice when shifting from 2AM EDT to 1AM EST:
250 (twice1 (ical:make-date-time :year 2025 :month 11 :day 2
251 :hour 1 :minute 0 :second 0))
252 (twice2 (ical:make-date-time :year 2025 :month 11 :day 2
253 :hour 1 :minute 30 :second 0))
254 ;; 12:59 AM, 2AM should not occur twice:
255 (once1 (ical:make-date-time :year 2025 :month 11 :day 2
256 :hour 0 :minute 59 :second 0
257 :zone ict:edt :dst t))
258 (once2 (ical:make-date-time :year 2025 :month 11 :day 2
259 :hour 2 :minute 0 :second 0
260 :zone ict:est :dst nil)))
261 (should (icr:date-time-occurs-twice-p twice1 std-onset ict:est-latest))
262 (should (icr:date-time-occurs-twice-p twice2 std-onset ict:est-latest))
263 (should-not
264 (icr:date-time-occurs-twice-p once1 std-onset ict:est-latest))
265 (should-not
266 (icr:date-time-occurs-twice-p once2 std-onset ict:est-latest))))
267
268(ert-deftest ict:recur-find-secondly-interval ()
269 "Does `icr:find-secondly-interval' find correct intervals?"
270 (let* ((dtstart (ical:make-date-time :year 2025 :month 1 :day 1
271 :hour 0 :minute 0 :second 0
272 ;; Use UTC for the tests with no
273 ;; time zone, so that the results
274 ;; don't depend on system's local time
275 :zone 0))
276 (dtstart/tz (ical:date-time-variant dtstart :zone ict:est :dst nil)))
277
278 ;; Year numbers are monotonically increasing in the following test cases,
279 ;; to make it easy to tell which of them fails.
280
281 ;; No timezone, just clock time, around a target that doesn't fall on
282 ;; an interval boundary:
283 (let* ((target (ical:date-time-variant dtstart :year 2026 :second 5 :zone 0))
284 (expected-int
285 (list
286 (ical:date-time-variant target :second 0 :tz 'preserve)
287 (ical:date-time-variant target :second 1 :tz 'preserve)
288 (ical:date-time-variant target :second 10 :tz 'preserve))))
289 (should
290 (equal expected-int
291 (icr:find-secondly-interval target dtstart 10))))
292
293 ;; No timezone, just clock time, around a target that does fall on
294 ;; an interval boundary:
295 (let* ((target (ical:date-time-variant dtstart :year 2027 :second 10 :zone 0))
296 (expected-int
297 (list
298 (ical:date-time-variant target :second 10 :tz 'preserve)
299 (ical:date-time-variant target :second 11 :tz 'preserve)
300 (ical:date-time-variant target :second 20 :tz 'preserve))))
301 (should
302 (equal expected-int
303 (icr:find-secondly-interval target dtstart 10))))
304
305 ;; With timezone, around a target that falls on an interval
306 ;; boundary, in the same observance:
307 (let* ((target (ical:date-time-variant dtstart/tz
308 :year 2028 :month 2 :second 20
309 :zone ict:est :dst nil))
310 (expected-int
311 (list
312 (ical:date-time-variant target :second 20 :tz 'preserve)
313 (ical:date-time-variant target :second 21 :tz 'preserve)
314 (ical:date-time-variant target :second 30 :tz 'preserve))))
315 (should
316 (equal expected-int
317 (icr:find-secondly-interval target dtstart/tz 10
318 ict:tz-eastern))))
319
320 ;; With timezone, around a target that does not fall on an interval
321 ;; boundary, and after the time zone observance shift:
322 (let* ((target (ical:date-time-variant dtstart/tz
323 :year 2029 :month 5 :second 30
324 :zone ict:edt :dst t))
325 (expected-int
326 (list
327 (ical:date-time-variant target :second 30 :tz 'preserve)
328 (ical:date-time-variant target :second 31 :tz 'preserve)
329 (ical:date-time-variant target :second 40 :tz 'preserve))))
330 (should
331 (equal expected-int
332 (icr:find-secondly-interval target dtstart/tz 10 ict:tz-eastern))))
333
334 ;; With timezone, around a target that falls into the gap in local
335 ;; times and thus does not exist as a local time. In this case, what
336 ;; is supposed to happen is that the clock time value in the [observance]
337 ;; recurrences "is interpreted using the UTC offset before the gap
338 ;; in local times." So we should get the same absolute times back,
339 ;; but re-decoded into the new observance, i.e., one hour later.
340 (let* ((target (ical:date-time-variant dtstart/tz
341 :year 2030 :month 3 :day 10
342 :hour 2 :minute 30 :second 0
343 :zone ict:est :dst nil))
344 (expected-int
345 (list
346 (ical:date-time-variant target :hour 3 :second 0
347 :zone ict:edt :dst t)
348 (ical:date-time-variant target :hour 3 :second 1
349 :zone ict:edt :dst t)
350 (ical:date-time-variant target
351 :hour 3 :second 10
352 :zone ict:edt :dst t))))
353 (should
354 (equal expected-int
355 (icr:find-secondly-interval target dtstart/tz 10 ict:tz-eastern))))
356
357 ;; With timezone, with a "pathological" interval size of 59 seconds.
358 ;; There should be no problem with this case, because the interval
359 ;; bounds calculation is done in absolute time, but it's annoying to
360 ;; calculate the expected interval by hand:
361 (let* ((target (ical:date-time-variant dtstart/tz
362 :year 2031 :month 4 :day 15
363 :hour 12 :minute 0 :second 0
364 :zone ict:edt :dst t))
365 (intsize 59)
366 (expected-int
367 (list
368 (ical:date-time-variant target :hour 11 :minute 59 :second 16
369 :tz 'preserve)
370 (ical:date-time-variant target :hour 11 :minute 59 :second 17
371 :tz 'preserve)
372 (ical:date-time-variant target :hour 12 :minute 0 :second 15
373 :tz 'preserve))))
374 (should
375 (equal expected-int
376 (icr:find-secondly-interval target dtstart/tz intsize
377 ict:tz-eastern))))))
378
379(ert-deftest ict:recur-find-minutely-interval ()
380 "Does `icr:find-minutely-interval' find correct intervals?"
381 (let* ((dtstart (ical:make-date-time :year 2025 :month 1 :day 1
382 :hour 0 :minute 0
383 ;; make sure intervals are
384 ;; bounded on whole minutes:
385 :second 23))
386 (dtstart/tz (ical:date-time-variant dtstart :zone ict:est :dst nil)))
387
388 ;; Year numbers are monotonically increasing in the following test cases,
389 ;; to make it easy to tell which of them fails.
390
391 ;; No timezone, just a fixed offset, around a target that doesn't fall on
392 ;; an interval boundary:
393 (let* ((target (ical:date-time-variant dtstart :year 2026 :minute 5))
394 (intsize 10)
395 (expected-int
396 (list
397 (ical:date-time-variant target :minute 0 :second 0)
398 (ical:date-time-variant target :minute 1 :second 0)
399 (ical:date-time-variant target :minute 10 :second 0))))
400 (should
401 (equal expected-int
402 (icr:find-minutely-interval target dtstart intsize))))
403
404 ;; No timezone, just clock time, around a target that does fall on
405 ;; an interval boundary:
406 (let* ((target (ical:date-time-variant dtstart :year 2027 :minute 10))
407 (intsize 10)
408 (expected-int
409 (list
410 (ical:date-time-variant target :minute 10 :second 0)
411 (ical:date-time-variant target :minute 11 :second 0)
412 (ical:date-time-variant target :minute 20 :second 0))))
413 (should
414 (equal expected-int
415 (icr:find-minutely-interval target dtstart intsize))))
416
417 ;; With timezone, around a target that falls on an interval
418 ;; boundary, in the same observance:
419 (let* ((target (ical:date-time-variant dtstart/tz
420 :year 2028 :month 2 :minute 20
421 :zone ict:est :dst nil))
422 (intsize 10)
423 (expected-int
424 (list
425 (ical:date-time-variant target :minute 20 :second 0
426 :zone ict:est :dst nil)
427 (ical:date-time-variant target :minute 21 :second 0
428 :zone ict:est :dst nil)
429 (ical:date-time-variant target :minute 30 :second 0
430 :zone ict:est :dst nil))))
431 (should
432 (equal expected-int
433 (icr:find-minutely-interval target dtstart/tz intsize
434 ict:tz-eastern))))
435
436 ;; With timezone, around a target that does not fall on an interval
437 ;; boundary, and after the time zone observance shift:
438 (let* ((target (ical:date-time-variant dtstart/tz
439 :year 2029 :month 5 :minute 30
440 :zone ict:edt :dst t))
441 (intsize 10)
442 (expected-int
443 (list
444 (ical:date-time-variant target :minute 30 :second 0
445 :zone ict:edt :dst t)
446 (ical:date-time-variant target :minute 31 :second 0
447 :zone ict:edt :dst t)
448 (ical:date-time-variant target :minute 40 :second 0
449 :zone ict:edt :dst t))))
450 (should
451 (equal expected-int
452 (icr:find-minutely-interval target dtstart/tz intsize
453 ict:tz-eastern))))
454
455 ;; With timezone, around a target that falls into the gap in local
456 ;; times and thus does not exist as a local time. In this case, what
457 ;; is supposed to happen is that the clock time value in the [observance]
458 ;; recurrences "is interpreted using the UTC offset before the gap
459 ;; in local times." So we should get the same absolute times back,
460 ;; but re-decoded into the new observance, i.e., one hour later.
461 (let* ((target (ical:date-time-variant dtstart/tz
462 :year 2030 :month 3 :day 10
463 :hour 2 :minute 30 :second 0
464 :zone ict:est :dst nil))
465 (intsize 10)
466 (expected-int
467 (list
468 (ical:date-time-variant target :hour 3 :minute 30 :second 0
469 :zone ict:edt :dst t)
470 (ical:date-time-variant target :hour 3 :minute 31 :second 0
471 :zone ict:edt :dst t)
472 (ical:date-time-variant target
473 :hour 3 :minute 40 :second 0
474 :zone ict:edt :dst t))))
475 (should
476 (equal expected-int
477 (icr:find-minutely-interval target dtstart/tz intsize
478 ict:tz-eastern))))))
479
480(ert-deftest ict:recur-find-hourly-interval ()
481 "Does `icr:find-hourly-interval' find correct intervals?"
482 (let* ((dtstart (ical:make-date-time :year 2025 :month 1 :day 1
483 :hour 0
484 ;; make sure intervals are bounded on
485 ;; whole hours:
486 :minute 11 :second 23))
487 (dtstart/tz (ical:date-time-variant dtstart :zone ict:est :dst nil)))
488
489 ;; Year numbers are monotonically increasing in the following test cases,
490 ;; to make it easy to tell which of them fails.
491 ;; No timezone, just clock time, around a target that doesn't fall on
492 ;; an interval boundary:
493 (let* ((target (ical:date-time-variant dtstart :year 2026 :hour 5))
494 (intsize 10)
495 (expected-int
496 (list
497 (ical:date-time-variant target :hour 0 :minute 0 :second 0)
498 (ical:date-time-variant target :hour 1 :minute 0 :second 0)
499 (ical:date-time-variant target :hour 10 :minute 0 :second 0))))
500 (should
501 (equal expected-int
502 (icr:find-hourly-interval target dtstart intsize))))
503
504 ;; No timezone, just clock time, around a target that does fall on
505 ;; an interval boundary:
506 (let* ((target (ical:date-time-variant dtstart :year 2027 :hour 10))
507 (intsize 10)
508 (expected-int
509 (list
510 (ical:date-time-variant target :hour 10 :minute 0 :second 0)
511 (ical:date-time-variant target :hour 11 :minute 0 :second 0)
512 (ical:date-time-variant target :hour 20 :minute 0 :second 0))))
513 (should
514 (equal expected-int
515 (icr:find-hourly-interval target dtstart intsize))))
516
517 ;; With timezone, around a target that falls on an interval
518 ;; boundary, in the same observance:
519 (let* ((target (ical:date-time-variant dtstart/tz
520 :year 2028 :month 2 :hour 10
521 :zone ict:est :dst nil))
522 (intsize 2)
523 (expected-int
524 (list
525 (ical:date-time-variant target :hour 10 :minute 0 :second 0
526 :zone ict:est :dst nil)
527 (ical:date-time-variant target :hour 11 :minute 0 :second 0
528 :zone ict:est :dst nil)
529 (ical:date-time-variant target :hour 12 :minute 0 :second 0
530 :zone ict:est :dst nil))))
531 (should
532 (equal expected-int
533 (icr:find-hourly-interval target dtstart/tz intsize
534 ict:tz-eastern))))
535
536 ;; With time zone, around a target that does not fall on an interval
537 ;; boundary, and after the time zone observance shift. Note that
538 ;; because of our decision to calculate with absolute times in
539 ;; SECONDLY/MINUTELY/HOURLY rules (see `icr:find-secondly-recurrence-rule')
540 ;; the interval clock times shift an hour here:
541 (let* ((target (ical:date-time-variant dtstart/tz
542 :year 2029 :month 5 :hour 12
543 :zone ict:edt :dst t))
544 (intsize 2)
545 (expected-int
546 (list
547 (ical:date-time-variant target :hour 11 :minute 0 :second 0
548 :zone ict:edt :dst t)
549 (ical:date-time-variant target :hour 12 :minute 0 :second 0
550 :zone ict:edt :dst t)
551 (ical:date-time-variant target :hour 13 :minute 0 :second 0
552 :zone ict:edt :dst t))))
553 (should
554 (equal expected-int
555 (icr:find-hourly-interval target dtstart/tz intsize
556 ict:tz-eastern))))
557
558 ;; With timezone, around a target that falls into the gap in local
559 ;; times and thus does not exist as a local time. In this case, what
560 ;; is supposed to happen is that the clock time value in the [observance]
561 ;; recurrences "is interpreted using the UTC offset before the gap
562 ;; in local times." So we should get the same absolute times back,
563 ;; but re-decoded into the new observance, i.e., one hour later.
564 (let* ((target (ical:make-date-time :year 2030 :month 3 :day 10
565 :hour 2 :minute 30 :second 30
566 :zone ict:est :dst nil))
567 (intsize 2)
568 (expected-int
569 (list
570 (ical:date-time-variant target :hour 3 :minute 0 :second 0
571 :zone ict:edt :dst t)
572 (ical:date-time-variant target :hour 4 :minute 0 :second 0
573 :zone ict:edt :dst t)
574 (ical:date-time-variant target :hour 5 :minute 0 :second 0
575 :zone ict:edt :dst t))))
576 (should
577 (equal expected-int
578 (icr:find-hourly-interval target dtstart/tz intsize
579 ict:tz-eastern))))))
580
581(ert-deftest ict:recur-find-daily-interval-w/date ()
582 "Does `icr:find-daily-interval' find correct date intervals?"
583 (let* ((dtstart (list 1 8 2025)))
584 ;; Since all the results should be the same after the initial
585 ;; calculation of the absolute dates DTSTART and TARGET, we just
586 ;; test one simple case here and test with date-times more
587 ;; thoroughly below.
588
589 ;; A target that doesn't fall on an interval boundary:
590 (let* ((target (list 1 9 2026))
591 (intsize 7)
592 (expected-int
593 (list
594 (ical:make-date-time :year 2026 :month 1 :day 7
595 :hour 0 :minute 0 :second 0)
596 (ical:make-date-time :year 2026 :month 1 :day 8
597 :hour 0 :minute 0 :second 0)
598 (ical:make-date-time :year 2026 :month 1 :day 14
599 :hour 0 :minute 0 :second 0))))
600 (should (equal expected-int
601 (icr:find-daily-interval target dtstart intsize))))))
602
603(ert-deftest ict:recur-find-daily-interval-w/date-time ()
604 "Does `icr:find-daily-interval' find correct date-time intervals?"
605 (let* ((dtstart (ical:make-date-time :year 2025 :month 1 :day 8 ; a Wednesday
606 ;; make sure intervals are bounded on
607 ;; whole days:
608 :hour 7 :minute 11 :second 23))
609 (dtstart/tz (ical:date-time-variant dtstart :zone ict:est :dst nil)))
610
611 ;; Year numbers are monotonically increasing in the following test cases,
612 ;; to make it easy to tell which of them fails.
613
614 ;; No timezone, just clock time, around a target that doesn't fall on
615 ;; an interval boundary:
616 (let* ((target (ical:date-time-variant dtstart
617 :year 2026 :month 1 :day 9))
618 (intsize 7)
619 (expected-int
620 (list
621 (ical:date-time-variant target :day 7 :hour 0 :minute 0 :second 0)
622 (ical:date-time-variant target :day 8 :hour 0 :minute 0 :second 0)
623 (ical:date-time-variant target :day 14
624 :hour 0 :minute 0 :second 0))))
625 (should
626 (equal expected-int
627 (icr:find-daily-interval target dtstart intsize))))
628
629 ;; No timezone, just clock time, around a target that does fall on
630 ;; an interval boundary:
631 (let* ((target (ical:date-time-variant dtstart :year 2027 :month 1 :day 6))
632 (intsize 7)
633 (expected-int
634 (list
635 (ical:date-time-variant target :day 6 :hour 0 :minute 0 :second 0)
636 (ical:date-time-variant target :day 7 :hour 0 :minute 0 :second 0)
637 (ical:date-time-variant target :day 13 :hour 0 :minute 0 :second 0))))
638 (should
639 (equal expected-int
640 (icr:find-daily-interval target dtstart intsize))))
641
642 ;; With timezone, around a target that falls on an interval
643 ;; boundary, in the same observance:
644 (let* ((target (ical:date-time-variant dtstart/tz :year 2028 :month 2 :day 2
645 :zone ict:est :dst nil))
646 (intsize 7)
647 (expected-int
648 (list
649 (ical:date-time-variant target :day 2 :hour 0 :minute 0 :second 0
650 :tz 'preserve)
651 (ical:date-time-variant target :day 3 :hour 0 :minute 0 :second 0
652 :tz 'preserve)
653 (ical:date-time-variant target :day 9 :hour 0 :minute 0 :second 0
654 :tz 'preserve))))
655 (should
656 (equal expected-int
657 (icr:find-daily-interval target dtstart/tz intsize ict:tz-eastern))))
658
659 ;; With time zone, around a target that does not fall on an interval
660 ;; boundary, and after the time zone observance shift.
661 (let* ((target (ical:date-time-variant dtstart/tz
662 :year 2029 :month 5 :day 28
663 :zone ict:edt :dst t))
664 (intsize 7)
665 (expected-int
666 (list
667 (ical:date-time-variant target :day 23 :hour 0 :minute 0 :second 0
668 :tz 'preserve)
669 (ical:date-time-variant target :day 24 :hour 0 :minute 0 :second 0
670 :tz 'preserve)
671 (ical:date-time-variant target :day 30 :hour 0 :minute 0 :second 0
672 :tz 'preserve))))
673 (should
674 (equal expected-int
675 (icr:find-daily-interval target dtstart/tz intsize
676 ict:tz-eastern))))))
677
678(ert-deftest ict:recur-find-weekly-interval-w/date ()
679 "Does `icr:find-weekly-interval' find correct date intervals?"
680 (let* ((dtstart '(1 8 2025)))
681 ;; Since all the results should be the same after the initial
682 ;; calculation of the absolute dates DTSTART and TARGET, we just
683 ;; test one simple case here and test with date-times more
684 ;; thoroughly below.
685
686 ;; A target that doesn't fall on an interval boundary:
687 (let* ((target '(1 9 2026))
688 (intsize 2)
689 (expected-int-mon
690 (list
691 (ical:make-date-time :year 2026 :month 1 :day 5
692 :hour 0 :minute 0 :second 0)
693 (ical:make-date-time :year 2026 :month 1 :day 12
694 :hour 0 :minute 0 :second 0)
695 (ical:make-date-time :year 2026 :month 1 :day 19
696 :hour 0 :minute 0 :second 0))))
697 (should (equal expected-int-mon
698 (icr:find-weekly-interval target dtstart intsize))))))
699
700(ert-deftest ict:recur-find-weekly-interval-w/date-time ()
701 "Does `icr:find-weekly-interval' find correct date-time intervals?"
702 (let* ((dtstart (ical:make-date-time :year 2025 :month 1 :day 8 ; a Wednesday
703 ;; make sure intervals are bounded on
704 ;; whole days:
705 :hour 7 :minute 11 :second 23)))
706
707 ;; Year numbers are monotonically increasing in the following test cases,
708 ;; to make it easy to tell which of them fails.
709
710 ;; No timezone, just clock time, around a target that doesn't fall on
711 ;; an interval boundary:
712 (let* ((target (ical:date-time-variant dtstart :year 2026 :month 1 :day 9))
713 (intsize 2)
714 (weds 3)
715 ;; expected interval for Monday (default) week start:
716 (expected-int-mon
717 (list
718 (ical:date-time-variant target :day 5 :hour 0 :minute 0 :second 0)
719 (ical:date-time-variant target :day 12 :hour 0 :minute 0 :second 0)
720 (ical:date-time-variant target :day 19 :hour 0 :minute 0 :second 0)))
721 ;; expected interval for Wednesday week start:
722 (expected-int-wed
723 (list
724 (ical:date-time-variant target :day 7 :hour 0 :minute 0 :second 0)
725 (ical:date-time-variant target :day 14 :hour 0 :minute 0 :second 0)
726 (ical:date-time-variant target :day 21 :hour 0 :minute 0 :second 0))))
727 (should
728 (equal expected-int-mon
729 (icr:find-weekly-interval target dtstart intsize)))
730 (should
731 (equal expected-int-wed
732 (icr:find-weekly-interval target dtstart intsize weds))))
733
734 ;; Around a target that does fall on an interval boundary, Monday week start:
735 (let* ((target (ical:date-time-variant dtstart :year 2027 :month 1 :day 4))
736 (intsize 3)
737 ;; expected interval for Monday (default) week start:
738 (expected-int-mon
739 (list
740 (ical:date-time-variant target :year 2026 :month 12 :day 21
741 :hour 0 :minute 0 :second 0)
742 (ical:date-time-variant target :year 2026 :month 12 :day 28
743 :hour 0 :minute 0 :second 0)
744 (ical:date-time-variant target :day 11
745 :hour 0 :minute 0 :second 0))))
746 (should
747 (equal expected-int-mon
748 (icr:find-weekly-interval target dtstart intsize))))
749
750 ;; Around a target that does fall on an interval boundary, Sunday week start:
751 (let* ((target (ical:date-time-variant dtstart :year 2028 :month 1 :day 2))
752 (intsize 3)
753 (sun 0)
754 ;; expected interval for Sunday week start:
755 (expected-int-sun
756 (list
757 (ical:date-time-variant target :day 2 :hour 0 :minute 0 :second 0)
758 (ical:date-time-variant target :day 9 :hour 0 :minute 0 :second 0)
759 (ical:date-time-variant target :day 23 :hour 0 :minute 0 :second 0))))
760 (should
761 (equal expected-int-sun
762 (icr:find-weekly-interval target dtstart intsize sun))))))
763
764(ert-deftest ict:recur-find-monthly-interval ()
765 "Does `icr:find-monthly-interval' find correct intervals?"
766 ;; Year numbers are monotonically increasing in the following test cases,
767 ;; to make it easy to tell which of them fails.
768
769 ;; One test with dates, to make sure that works:
770 (let* ((dtstart '(1 8 2025))
771 (target '(10 9 2025))
772 (intsize 5)
773 (expected-int
774 (list
775 (ical:make-date-time :year 2025 :month 6 :day 1
776 :hour 0 :minute 0 :second 0)
777 (ical:make-date-time :year 2025 :month 7 :day 1
778 :hour 0 :minute 0 :second 0)
779 (ical:make-date-time :year 2025 :month 11 :day 1
780 :hour 0 :minute 0 :second 0))))
781 (should (equal expected-int
782 (icr:find-monthly-interval target dtstart intsize))))
783
784 ;; Around a target that doesn't fall on an interval boundary:
785 (let* ((dtstart (ical:make-date-time :year 2025 :month 1 :day 1
786 ;; make sure intervals are bounded on
787 ;; whole days:
788 :hour 7 :minute 11 :second 23))
789 (target (ical:date-time-variant dtstart :year 2026 :month 3 :day 9))
790 (intsize 2)
791 (expected-int
792 (list
793 (ical:date-time-variant target :day 1 :hour 0 :minute 0 :second 0)
794 (ical:date-time-variant target :month 4 :day 1
795 :hour 0 :minute 0 :second 0)
796 (ical:date-time-variant target :month 5 :day 1
797 :hour 0 :minute 0 :second 0))))
798 (should
799 (equal expected-int
800 (icr:find-monthly-interval target dtstart intsize))))
801
802 ;; Around a target that does fall on an interval boundary:
803 (let* ((dtstart (ical:make-date-time :year 2025 :month 1 :day 1
804 ;; make sure intervals are bounded on
805 ;; whole days:
806 :hour 7 :minute 11 :second 23))
807 (target (ical:date-time-variant dtstart :year 2027 :month 5 :day 1))
808 (intsize 7)
809 (expected-int
810 (list
811 (ical:date-time-variant target :year 2027 :month 5 :day 1
812 :hour 0 :minute 0 :second 0)
813 (ical:date-time-variant target :year 2027 :month 6 :day 1
814 :hour 0 :minute 0 :second 0)
815 (ical:date-time-variant target :year 2027 :month 12 :day 1
816 :hour 0 :minute 0 :second 0))))
817 (should
818 (equal expected-int
819 (icr:find-monthly-interval target dtstart intsize))))
820
821 ;; Around a target that does not fall on an interval boundary, where
822 ;; start month > target month
823 (let* ((dtstart (ical:make-date-time :year 2028 :month 11 :day 11
824 :hour 11 :minute 11 :second 11))
825 (target (ical:date-time-variant dtstart
826 :year 2029 :month 4 :day 15))
827 (intsize 2)
828 (expected-int
829 (list
830 (ical:date-time-variant target :year 2029 :month 3 :day 1
831 :hour 0 :minute 0 :second 0)
832 (ical:date-time-variant target :year 2029 :month 4 :day 1
833 :hour 0 :minute 0 :second 0)
834 (ical:date-time-variant target :year 2029 :month 5 :day 1
835 :hour 0 :minute 0 :second 0))))
836 (should
837 (equal expected-int
838 (icr:find-monthly-interval target dtstart intsize))))
839
840 ;; Around a target that falls on an interval boundary, where
841 ;; start month > target month
842 (let* ((dtstart (ical:make-date-time :year 2029 :month 11 :day 11
843 :hour 11 :minute 11 :second 11 ))
844 (target (ical:date-time-variant dtstart
845 :year 2030 :month 5 :day 1))
846 (intsize 2)
847 (expected-int
848 (list
849 (ical:date-time-variant target :year 2030 :month 5 :day 1
850 :hour 0 :minute 0 :second 0)
851 (ical:date-time-variant target :year 2030 :month 6 :day 1
852 :hour 0 :minute 0 :second 0)
853 (ical:date-time-variant target :year 2030 :month 7 :day 1
854 :hour 0 :minute 0 :second 0))))
855 (should
856 (equal expected-int
857 (icr:find-monthly-interval target dtstart intsize))))
858
859 ;; Around a target that falls on an interval boundary, where
860 ;; start month = target month
861 (let* ((dtstart (ical:make-date-time :year 2031 :month 11 :day 11
862 :hour 11 :minute 11 :second 11 ))
863 (target (ical:date-time-variant dtstart :year 2032 :month 11 :day 11))
864 (intsize 2)
865 (expected-int
866 (list
867 (ical:date-time-variant target :year 2032 :month 11 :day 1
868 :hour 0 :minute 0 :second 0)
869 (ical:date-time-variant target :year 2032 :month 12 :day 1
870 :hour 0 :minute 0 :second 0)
871 (ical:date-time-variant target :year 2033 :month 1 :day 1
872 :hour 0 :minute 0 :second 0))))
873 (should
874 (equal expected-int
875 (icr:find-monthly-interval target dtstart intsize)))))
876
877(ert-deftest ict:recur-find-yearly-interval ()
878 "Does `icr:find-yearly-interval' find correct date intervals?"
879 ;; Year numbers are monotonically increasing in the following test cases,
880 ;; to make it easy to tell which of them fails.
881
882 ;; One test with dates, to make sure that works:
883 (let* ((dtstart '(1 8 2025))
884 (target '(10 9 2025))
885 (intsize 2)
886 (expected-int
887 (list
888 (ical:make-date-time :year 2025 :month 1 :day 1
889 :hour 0 :minute 0 :second 0)
890 (ical:make-date-time :year 2026 :month 1 :day 1
891 :hour 0 :minute 0 :second 0)
892 (ical:make-date-time :year 2027 :month 1 :day 1
893 :hour 0 :minute 0 :second 0))))
894 (should (equal expected-int
895 (icr:find-yearly-interval target dtstart intsize))))
896
897 ;; A target not on an interval boundary:
898 (let* ((dtstart (ical:make-date-time :year 2026 :month 3 :day 1
899 :hour 1 :minute 2 :second 3))
900 (target (ical:make-date-time :year 2026 :month 7 :day 28
901 :hour 11 :minute 58 :second 0))
902 (intsize 3)
903 (expected-int
904 (list
905 (ical:make-date-time :year 2026 :month 1 :day 1
906 :hour 0 :minute 0 :second 0)
907 (ical:make-date-time :year 2027 :month 1 :day 1
908 :hour 0 :minute 0 :second 0)
909 (ical:make-date-time :year 2029 :month 1 :day 1
910 :hour 0 :minute 0 :second 0))))
911 (should (equal expected-int
912 (icr:find-yearly-interval target dtstart intsize))))
913
914 ;; A target on an interval boundary:
915 (let* ((dtstart (ical:make-date-time :year 2027 :month 3 :day 1
916 :hour 1 :minute 2 :second 3))
917 (target (ical:make-date-time :year 2028 :month 1 :day 1
918 :hour 0 :minute 0 :second 0))
919 (intsize 4)
920 (expected-int
921 (list
922 (ical:make-date-time :year 2027 :month 1 :day 1
923 :hour 0 :minute 0 :second 0)
924 (ical:make-date-time :year 2028 :month 1 :day 1
925 :hour 0 :minute 0 :second 0)
926 (ical:make-date-time :year 2031 :month 1 :day 1
927 :hour 0 :minute 0 :second 0))))
928 (should (equal expected-int
929 (icr:find-yearly-interval target dtstart intsize))))
930
931 ;; A target earlier than dtstart but in the same year;
932 ;; it's important that this works when looking up recurrences of
933 ;; time zone observance onsets
934 (let* ((dtstart (ical:make-date-time :year 2029 :month 5 :day 28
935 :hour 1 :minute 2 :second 3))
936 (target (ical:make-date-time :year 2029 :month 2 :day 14
937 :hour 11 :minute 58 :second 0))
938 (intsize 1)
939 (expected-int
940 (list
941 (ical:make-date-time :year 2029 :month 1 :day 1
942 :hour 0 :minute 0 :second 0)
943 (ical:make-date-time :year 2030 :month 1 :day 1
944 :hour 0 :minute 0 :second 0)
945 (ical:make-date-time :year 2030 :month 1 :day 1
946 :hour 0 :minute 0 :second 0))))
947 (should (equal expected-int
948 (icr:find-yearly-interval target dtstart intsize)))))
949
950;; Subintervals:
951
952(ert-deftest ict:recur-refine-byyearday ()
953 "Does `icr:refine-byyearday' correctly refine by yeardays?"
954 (let* ((low (ical:make-date-time :year 2025 :month 1 :day 1
955 :hour 0 :minute 0 :second 0))
956 (high (ical:date/time-add low :year 1))
957 (interval (list low high high))
958 (yeardays (list 2 -7))
959 (sub1 (list (ical:date-time-variant low :day 2)
960 (ical:date-time-variant low :day 3)))
961 (sub2 (list (ical:date-time-variant low :month 12 :day 25)
962 (ical:date-time-variant low :month 12 :day 26)))
963 (expected-subintervals (list sub1 sub2)))
964 (should (equal expected-subintervals
965 (icr:refine-byyearday interval yeardays)))))
966
967(ert-deftest ict:recur-refine-bymonth ()
968 "Does `icr:refine-bymonth' correctly refine by months?"
969 (let* ((low (ical:make-date-time :year 2025 :month 1 :day 1
970 :hour 0 :minute 0 :second 0))
971 (high (ical:date/time-add low :year 1))
972 (interval (list low high high))
973 (months (list 9 2))
974 (sub1 (list (ical:date-time-variant low :month 2 :day 1)
975 (ical:date-time-variant low :month 3 :day 1)))
976 (sub2 (list (ical:date-time-variant low :month 9 :day 1)
977 (ical:date-time-variant low :month 10 :day 1)))
978 (expected-subintervals (list sub1 sub2)))
979 (should (equal expected-subintervals
980 (icr:refine-bymonth interval months)))))
981
982(ert-deftest ict:recur-refine-bymonthday ()
983 "Does `icr:refine-bymonthday' correctly refine by days of the month?"
984 (let* ((low (ical:make-date-time :year 2025 :month 2 :day 1
985 :hour 0 :minute 0 :second 0))
986 (high (ical:date/time-add low :month 1))
987 (interval (list low high high))
988 (monthdays (list -1 2 29))
989 ;; N.B. we should get no subinterval for Feb. 29, 2025
990 (sub1 (list (ical:date-time-variant low :day 2)
991 (ical:date-time-variant low :day 3)))
992 (sub2 (list (ical:date-time-variant low :day 28)
993 (ical:date-time-variant low :month 3 :day 1)))
994 (expected-subintervals (list sub1 sub2)))
995 (should (equal expected-subintervals
996 (icr:refine-bymonthday interval monthdays)))))
997
998(ert-deftest ict:recur-refine-byday ()
999 "Does `icr:refine-byday' correctly refine by days of the week?"
1000 ;; The simple case: just day names
1001 (let* ((low (ical:make-date-time :year 2025 :month 3 :day 3 ; a Monday
1002 :hour 0 :minute 0 :second 0))
1003 (high (ical:date/time-add low :day 7))
1004 (interval (list low high high))
1005 (days (list 0 6)) ; just the weekend, please!
1006 (sub1 (list (ical:date-time-variant low :day 8)
1007 (ical:date-time-variant low :day 9)))
1008 (sub2 (list (ical:date-time-variant low :day 9)
1009 (ical:date-time-variant low :day 10)))
1010 (expected-subintervals (list sub1 sub2)))
1011 (should (equal expected-subintervals
1012 (icr:refine-byday interval days))))
1013
1014 ;; Day names with offsets within the month
1015 (let* ((low (ical:make-date-time :year 2025 :month 3 :day 1 ; a Saturday
1016 :hour 0 :minute 0 :second 0))
1017 (high (ical:date/time-add low :month 1))
1018 (interval (list low high high))
1019 (days (list '(1 . 2) '(1 . -1))) ; second and last Monday
1020 (sub1 (list (ical:date-time-variant low :day 10)
1021 (ical:date-time-variant low :day 11)))
1022 (sub2 (list (ical:date-time-variant low :day 31)
1023 (ical:date-time-variant low :month 4 :day 1)))
1024 (expected-subintervals (list sub1 sub2)))
1025 (should (equal expected-subintervals
1026 (icr:refine-byday interval days t))))
1027
1028 ;; Day names with offsets within the year
1029 (let* ((low (ical:make-date-time :year 2025 :month 1 :day 1
1030 :hour 0 :minute 0 :second 0))
1031 (high (ical:date/time-add low :year 1))
1032 (interval (list low high high))
1033 (days (list '(5 . 1) '(5 . -1))) ; first and last Friday
1034 (sub1 (list (ical:date-time-variant low :day 3)
1035 (ical:date-time-variant low :day 4)))
1036 (sub2 (list (ical:date-time-variant low :month 12 :day 26)
1037 (ical:date-time-variant low :month 12 :day 27)))
1038 (expected-subintervals (list sub1 sub2)))
1039 (should (equal expected-subintervals
1040 (icr:refine-byday interval days nil)))))
1041
1042(ert-deftest ict:recur-refine-byhour ()
1043 "Does `icr:refine-byhour' correctly refine by hours?"
1044 ;; No time zone, just clock times:
1045 (let* ((low (ical:make-date-time :year 2025 :month 1 :day 1
1046 :hour 0 :minute 0 :second 0))
1047 (high (ical:date/time-add low :day 1))
1048 (interval (list low high high))
1049 (hours (list 2 19))
1050 (sub1 (list (ical:date-time-variant low :hour 2)
1051 (ical:date-time-variant low :hour 3)))
1052 (sub2 (list (ical:date-time-variant low :hour 19)
1053 (ical:date-time-variant low :hour 20)))
1054 (expected-subintervals (list sub1 sub2)))
1055 (should (equal expected-subintervals
1056 (icr:refine-byhour interval hours))))
1057
1058 ;; With time zone, but without crossing an observance boundary:
1059 (let* ((low (ical:make-date-time :year 2025 :month 2 :day 1
1060 :hour 0 :minute 0 :second 0
1061 :zone ict:est :dst nil))
1062 (high (ical:date/time-add low :day 1 ict:tz-eastern))
1063 (interval (list low high high))
1064 (hours (list 2 19))
1065 (sub1 (list (ical:date-time-variant low :hour 2 :tz 'preserve)
1066 (ical:date-time-variant low :hour 3 :tz 'preserve)))
1067 (sub2 (list (ical:date-time-variant low :hour 19 :tz 'preserve)
1068 (ical:date-time-variant low :hour 20 :tz 'preserve)))
1069 (expected-subintervals (list sub1 sub2)))
1070 (should (equal expected-subintervals
1071 (icr:refine-byhour interval hours ict:tz-eastern)))))
1072
1073(ert-deftest ict:recur-refine-byminute ()
1074 "Does `icr:refine-byminute' correctly refine by minutes?"
1075 ;; No time zone, just clock times:
1076 (let* ((low (ical:make-date-time :year 2025 :month 5 :day 1
1077 :hour 13 :minute 0 :second 0))
1078 (high (ical:date/time-add low :hour 1))
1079 (interval (list low high high))
1080 (minutes (list 7 59))
1081 (sub1 (list (ical:date-time-variant low :minute 7)
1082 (ical:date-time-variant low :minute 8)))
1083 (sub2 (list (ical:date-time-variant low :minute 59)
1084 (ical:date-time-variant low :hour 14 :minute 0)))
1085 (expected-subintervals (list sub1 sub2)))
1086 (should (equal expected-subintervals
1087 (icr:refine-byminute interval minutes))))
1088
1089 ;; With time zone, but without crossing an observance boundary:
1090 (let* ((low (ical:make-date-time :year 2025 :month 2 :day 1
1091 :hour 13 :minute 0 :second 0
1092 :zone ict:est :dst nil))
1093 (high (ical:date/time-add low :hour 1 ict:tz-eastern))
1094 (interval (list low high high))
1095 (minutes (list 7 59))
1096 (sub1 (list (ical:date-time-variant low :minute 7 :tz 'preserve)
1097 (ical:date-time-variant low :minute 8 :tz 'preserve)))
1098 (sub2 (list (ical:date-time-variant low :minute 59 :tz 'preserve)
1099 (ical:date-time-variant low :hour 14 :minute 0
1100 :tz 'preserve)))
1101 (expected-subintervals (list sub1 sub2)))
1102 (should (equal expected-subintervals
1103 (icr:refine-byminute interval minutes ict:tz-eastern)))))
1104
1105(ert-deftest ict:recur-refine-bysecond ()
1106 "Does `icr:refine-bysecond' correctly refine by seconds?"
1107 ;; No time zone, just clock times:
1108 (let* ((low (ical:make-date-time :year 2025 :month 5 :day 1
1109 :hour 13 :minute 59 :second 0))
1110 (high (ical:date/time-add low :minute 1))
1111 (interval (list low high high))
1112 (seconds (list 24 59))
1113 (sub1 (list (ical:date-time-variant low :second 24)
1114 (ical:date-time-variant low :second 25)))
1115 (sub2 (list (ical:date-time-variant low :second 59)
1116 (ical:date-time-variant low :hour 14 :minute 0 :second 0)))
1117 (expected-subintervals (list sub1 sub2)))
1118 (should (equal expected-subintervals
1119 (icr:refine-bysecond interval seconds))))
1120
1121 ;; With time zone, but without crossing an observance boundary:
1122 (let* ((low (ical:make-date-time :year 2025 :month 2 :day 1
1123 :hour 13 :minute 19 :second 0
1124 :zone ict:est :dst nil))
1125 (high (ical:date/time-add low :minute 1 ict:tz-eastern))
1126 (interval (list low high high))
1127 (seconds (list 24 59))
1128 (sub1 (list (ical:date-time-variant low :second 24 :tz 'preserve)
1129 (ical:date-time-variant low :second 25 :tz 'preserve)))
1130 (sub2 (list (ical:date-time-variant low :second 59 :tz 'preserve)
1131 (ical:date-time-variant low :minute 20 :second 0
1132 :tz 'preserve)))
1133 (expected-subintervals (list sub1 sub2)))
1134 (should (equal expected-subintervals
1135 (icr:refine-bysecond interval seconds ict:tz-eastern)))))
1136
1137(ert-deftest ict:recur-subintervals-to-dates ()
1138 "Does `icr:subintervals-to-dates' correctly generate recurrences?"
1139 ;; Two subintervals, the first three days long, the second less than a single day
1140 (let* ((low1 (ical:make-date-time :year 2025 :month 5 :day 1
1141 :hour 13 :minute 59 :second 0))
1142 (high1 (ical:date/time-add low1 :day 3))
1143 (sub1 (list low1 high1))
1144 (low2 (ical:make-date-time :year 2025 :month 5 :day 31
1145 :hour 14 :minute 0 :second 0))
1146 (high2 (ical:date/time-add low2 :hour 3)) ; later but on the same day
1147 (sub2 (list low2 high2))
1148 (low-date1 (ical:date-time-to-date low1))
1149 (low-date2 (ical:date-time-to-date low2))
1150 (expected-recs (list low-date1
1151 (ical:date/time-add low-date1 :day 1)
1152 (ical:date/time-add low-date1 :day 2)
1153 (ical:date/time-add low-date1 :day 3)
1154 low-date2)))
1155 (should (equal expected-recs
1156 (icr:subintervals-to-dates (list sub1 sub2))))))
1157
1158(ert-deftest ict:recur-subintervals-to-date-times ()
1159 "Does `icr:subintervals-to-date-times' correctly generate recurrences?"
1160 ;; Two subintervals, each one second long, no time zone
1161 (let* ((low1 (ical:make-date-time :year 2025 :month 5 :day 1
1162 :hour 13 :minute 59 :second 0))
1163 (high1 (ical:date/time-add low1 :second 1))
1164 (sub1 (list low1 high1))
1165 (low2 (ical:make-date-time :year 2025 :month 5 :day 2
1166 :hour 14 :minute 0 :second 0))
1167 (high2 (ical:date/time-add low2 :second 1))
1168 (sub2 (list low2 high2))
1169 (expected-recs (list low1 low2)))
1170 (should (equal expected-recs
1171 (icr:subintervals-to-date-times (list sub1 sub2)))))
1172
1173 ;; A subinterval five seconds long, with time zone
1174 (let* ((low1 (ical:make-date-time :year 2025 :month 6 :day 1
1175 :hour 13 :minute 59 :second 0
1176 :zone ict:edt :dst t))
1177 (high1 (ical:date/time-add low1 :second 5 ict:tz-eastern))
1178 (sub1 (list low1 high1))
1179 (expected-recs
1180 (list low1
1181 (ical:date/time-add low1 :second 1 ict:tz-eastern)
1182 (ical:date/time-add low1 :second 2 ict:tz-eastern)
1183 (ical:date/time-add low1 :second 3 ict:tz-eastern)
1184 (ical:date/time-add low1 :second 4 ict:tz-eastern))))
1185 (should (equal expected-recs
1186 (icr:subintervals-to-date-times (list sub1) ict:tz-eastern))))
1187
1188 ;; A subinterval five seconds long, with time zone, which crosses an
1189 ;; observance boundary where the final three seconds occur after
1190 ;; clocks are set forward an hour; these seconds should therefore be in EDT:
1191 (let* ((low1 (ical:make-date-time :year 2025 :month 3 :day 9
1192 :hour 1 :minute 59 :second 58
1193 :zone ict:est :dst nil))
1194 (high1 (ical:make-date-time :year 2025 :month 3 :day 9
1195 :hour 3 :minute 0 :second 3
1196 :zone ict:edt :dst t))
1197 (sub1 (list low1 high1))
1198 (expected-recs
1199 (list low1
1200 (ical:date-time-variant low1 :second 59 :tz 'preserve)
1201 (ical:date-time-variant high1 :second 0 :tz 'preserve)
1202 (ical:date-time-variant high1 :second 1 :tz 'preserve)
1203 (ical:date-time-variant high1 :second 2 :tz 'preserve))))
1204 (should (equal expected-recs
1205 (icr:subintervals-to-date-times (list sub1) ict:tz-eastern))))
1206
1207 ;; A subinterval five seconds long, with time zone, which crosses an
1208 ;; observance boundary where the final three seconds occur after
1209 ;; clocks are set back an hour; these seconds should therefore be in
1210 ;; EST:
1211 (let* ((low1 (ical:make-date-time :year 2024 :month 11 :day 3
1212 :hour 1 :minute 59 :second 58
1213 :zone ict:edt :dst t))
1214 (high1 (ical:make-date-time :year 2024 :month 11 :day 3
1215 :hour 1 :minute 0 :second 2
1216 :zone ict:est :dst nil))
1217 (sub1 (list low1 high1))
1218 (expected-recs
1219 (list low1
1220 (ical:date-time-variant low1 :second 59 :tz 'preserve)
1221 (ical:date-time-variant high1 :second 0 :tz 'preserve)
1222 (ical:date-time-variant high1 :second 1 :tz 'preserve))))
1223 (should (equal expected-recs
1224 (icr:subintervals-to-date-times (list sub1) ict:tz-eastern)))))
1225
1226;; Tests for time zone functions:
1227
1228(ert-deftest ict:recur-tz-observance-on/nonexistent ()
1229 "Does `icr:tz-observance-on' correctly interpret nonexistent times?"
1230 (let* ((onset-start (ical:make-date-time :year 2030 :month 3 :day 10
1231 :hour 2 :minute 0 :second 0
1232 :zone ict:est :dst nil))
1233 (start-shifted (ical:date-time-variant onset-start :hour 3
1234 :zone ict:edt :dst t))
1235 ;; 2:30AM falls into the gap when the clock jumps from 2AM to 3AM:
1236 (nonexistent (ical:date-time-variant onset-start :minute 30
1237 :zone ict:est :dst nil))
1238 (nonexistent-shifted (ical:date-time-variant nonexistent :hour 3
1239 :zone ict:edt :dst t)))
1240 (icr:tz-observance-on onset-start ict:tz-eastern t) ;; updates the time to EDT
1241 (icr:tz-observance-on nonexistent ict:tz-eastern t) ;; updates the time to EDT
1242 (should (equal onset-start start-shifted))
1243 (should (equal nonexistent nonexistent-shifted))))
1244
1245(ert-deftest ict:recur-tz-observance-on/occurs-twice ()
1246 "Does `icr:tz-observance-on' correctly interpret times that occur twice?"
1247 (let* ((onset-start (ical:make-date-time :year 2025 :month 11 :day 2
1248 :hour 2 :minute 0 :second 0
1249 :zone ict:edt :dst t))
1250 ;; 1:30AM occurs twice when the clock is set back from 2AM to 1AM:
1251 (no-zone (ical:date-time-variant onset-start :hour 1 :minute 30))
1252 (first (ical:date-time-variant onset-start :hour 1 :minute 30
1253 :zone ict:edt :dst t))
1254 (second (ical:date-time-variant first :zone ict:est :dst nil))
1255 (first+1h (ical:date/time-add first :hour 1 ict:tz-eastern)))
1256 (icr:tz-observance-on no-zone ict:tz-eastern t) ;; sets zone
1257 (should (equal first no-zone))
1258 (should (equal second first+1h))))
1259
1260(ert-deftest ict:recur-tz-observance-on ()
1261 "Does `icr:tz-observance-on' correctly find observances?"
1262
1263 ;; A date before the start of all observances in the timezone.
1264 ;; In this case, there is no matching observance, so we should get nil.
1265 (let* ((dt (ical:make-date-time :year 1900 :month 1 :day 1
1266 :hour 12 :minute 0 :second 0
1267 :zone ict:est :dst nil))
1268 (ts (encode-time dt)))
1269 (should (null (icr:tz-observance-on dt ict:tz-eastern)))
1270 (should (null (icr:tz-observance-on ts ict:tz-eastern))))
1271
1272 ;; A date matching the start of one of the STANDARD observances:
1273 (let* ((dt (ical:make-date-time :year 1967 :month 10 :day 29
1274 :hour 2 :minute 0 :second 0
1275 :zone ict:edt :dst t))
1276 (obs/onset (icr:tz-observance-on dt ict:tz-eastern))
1277 (obs (car obs/onset))
1278 (onset (cadr obs/onset))
1279 ;; make sure we get the same result with an absolute time:
1280 (ts (encode-time dt))
1281 (ts-obs/onset (icr:tz-observance-on ts ict:tz-eastern)))
1282 (should (eq 'ical:standard (ical:ast-node-type obs)))
1283 (should (equal dt onset))
1284 (should (equal obs/onset ts-obs/onset)))
1285
1286 ;; A date matching the start of a DAYLIGHT observance:
1287 (let* ((dt (ical:make-date-time :year 1967 :month 4 :day 30
1288 :hour 2 :minute 0 :second 0
1289 :zone ict:est :dst nil))
1290 (obs/onset (icr:tz-observance-on dt ict:tz-eastern))
1291 (obs (car obs/onset))
1292 (onset (cadr obs/onset))
1293 ;; make sure we get the same result with an absolute time:
1294 (ts (encode-time dt))
1295 (ts-obs/onset (icr:tz-observance-on ts ict:tz-eastern)))
1296 (should (eq 'ical:daylight (ical:ast-node-type obs)))
1297 (should (equal dt onset))
1298 (should (equal obs/onset ts-obs/onset)))
1299
1300 ;; A date matching an RDATE of a DAYLIGHT observance:
1301 (let* ((dt (ical:make-date-time :year 1975 :month 2 :day 23
1302 :hour 2 :minute 0 :second 0
1303 :zone ict:est :dst nil))
1304 (obs/onset (icr:tz-observance-on dt ict:tz-eastern))
1305 (obs (car obs/onset))
1306 (onset (cadr obs/onset))
1307 ;; make sure we get the same result with an absolute time:
1308 (ts (encode-time dt))
1309 (ts-obs/onset (icr:tz-observance-on ts ict:tz-eastern)))
1310 (should (eq 'ical:daylight (ical:ast-node-type obs)))
1311 (should (equal dt onset))
1312 (should (equal obs/onset ts-obs/onset)))
1313
1314 ;; A date matching the end of a STANDARD observance:
1315 (let* ((ut (ical:make-date-time :year 2006 :month 10 :day 29
1316 :hour 6 :minute 0 :second 0
1317 :zone 0 :dst nil)) ; UNTIL is in UTC
1318 (dt (ical:make-date-time :year 2006 :month 10 :day 29
1319 :hour 2 :minute 0 :second 0
1320 :zone ict:edt :dst t))
1321 (obs/onset (icr:tz-observance-on dt ict:tz-eastern))
1322 (obs (car obs/onset))
1323 (onset (cadr obs/onset))
1324 ;; make sure we get the same result with an absolute time:
1325 (ts (encode-time dt))
1326 (ts-obs/onset (icr:tz-observance-on ts ict:tz-eastern)))
1327 (should (ical:date-time-simultaneous-p ut dt))
1328 (should (eq 'ical:standard (ical:ast-node-type obs)))
1329 (should (equal dt onset))
1330 (should (equal obs/onset ts-obs/onset)))
1331
1332 ;; A date matching the end of a DAYLIGHT observance:
1333 (let* ((ut (ical:make-date-time :year 2006 :month 4 :day 2
1334 :hour 7 :minute 0 :second 0
1335 :zone 0 :dst nil)) ; UNTIL is in UTC
1336 (dt (ical:make-date-time :year 2006 :month 4 :day 2
1337 :hour 2 :minute 0 :second 0
1338 :zone ict:est :dst nil))
1339 (obs/onset (icr:tz-observance-on dt ict:tz-eastern))
1340 (obs (car obs/onset))
1341 (onset (cadr obs/onset))
1342 ;; make sure we get the same result with an absolute time:
1343 (ts (encode-time dt))
1344 (ts-obs/onset (icr:tz-observance-on ts ict:tz-eastern)))
1345 (should (ical:date-time-simultaneous-p ut dt))
1346 (should (eq 'ical:daylight (ical:ast-node-type obs)))
1347 (should (equal dt onset))
1348 (should (equal obs/onset ts-obs/onset)))
1349
1350 ;; A date matching an onset in the middle of a DAYLIGHT observance
1351 ;; which has ended:
1352 (let* ((dt (ical:make-date-time :year 1980 :month 4 :day 27
1353 :hour 2 :minute 0 :second 0
1354 :zone ict:est :dst nil))
1355 (end (ical:make-date-time :year 1986 :month 4 :day 27
1356 :hour 7 :minute 0 :second 0
1357 :zone 0)) ; UNTIL is in UTC
1358 (obs/onset (icr:tz-observance-on dt ict:tz-eastern))
1359 (obs (car obs/onset))
1360 (onset (cadr obs/onset))
1361 ;; make sure we get the same result with an absolute time:
1362 (ts (encode-time dt))
1363 (ts-obs/onset (icr:tz-observance-on ts ict:tz-eastern)))
1364 (should (eq 'ical:daylight (ical:ast-node-type obs)))
1365 (should (equal dt onset))
1366 (should (equal end (ical:recur-until
1367 (ical:with-property-of obs 'ical:rrule nil value))))
1368 (should (equal obs/onset ts-obs/onset)))
1369
1370 ;; A date matching an onset of the DAYLIGHT observance which is
1371 ;; ongoing:
1372 (let* ((dt (ical:make-date-time :year 2025 :month 3 :day 9
1373 :hour 2 :minute 0 :second 0
1374 :zone ict:est :dst nil))
1375 (obs/onset (icr:tz-observance-on dt ict:tz-eastern))
1376 (obs (car obs/onset))
1377 (onset (cadr obs/onset))
1378 ;; make sure we get the same result with an absolute time:
1379 (ts (encode-time dt))
1380 (ts-obs/onset (icr:tz-observance-on ts ict:tz-eastern)))
1381 (should (eq 'ical:daylight (ical:ast-node-type obs)))
1382 (should (equal dt onset))
1383 (should (equal obs/onset ts-obs/onset)))
1384
1385 ;; A date in the middle of the DAYLIGHT observance which is ongoing:
1386 (let* ((start (ical:make-date-time :year 2025 :month 3 :day 9
1387 :hour 2 :minute 0 :second 0
1388 :zone ict:est :dst nil))
1389 (dt (ical:make-date-time :year 2025 :month 5 :day 28
1390 :hour 2 :minute 0 :second 0
1391 :zone ict:edt :dst t))
1392 (obs/onset (icr:tz-observance-on dt ict:tz-eastern))
1393 (obs (car obs/onset))
1394 (onset (cadr obs/onset))
1395 ;; make sure we get the same result with an absolute time:
1396 (ts (encode-time dt))
1397 (ts-obs/onset (icr:tz-observance-on ts ict:tz-eastern)))
1398 (should (eq 'ical:daylight (ical:ast-node-type obs)))
1399 (should (equal start onset))
1400 (should (equal obs/onset ts-obs/onset)))
1401
1402 ;; A date in the middle of the STANDARD observance which is ongoing:
1403 (let* ((start (ical:make-date-time :year 2025 :month 11 :day 2
1404 :hour 2 :minute 0 :second 0
1405 :zone ict:edt :dst t))
1406 (dt (ical:make-date-time :year 2026 :month 1 :day 28
1407 :hour 12 :minute 30 :second 0
1408 :zone ict:est :dst nil))
1409 (obs/onset (icr:tz-observance-on dt ict:tz-eastern))
1410 (obs (car obs/onset))
1411 (onset (cadr obs/onset))
1412 ;; make sure we get the same result with an absolute time:
1413 (ts (encode-time dt))
1414 (ts-obs/onset (icr:tz-observance-on ts ict:tz-eastern)))
1415 (should (eq 'ical:standard (ical:ast-node-type obs)))
1416 (should (equal start onset))
1417 (should (equal obs/onset ts-obs/onset)))
1418
1419 ;; The following two tests were useful in detecting a broken optimization:
1420 (let* ((start (ical:make-date-time :year 2006 :month 10 :day 29
1421 :hour 2 :minute 0 :second 0
1422 :zone ict:edt :dst t))
1423 (dt (ical:make-date-time :year 2006 :month 11 :day 1
1424 :hour 12 :minute 30 :second 0
1425 :zone ict:est :dst nil))
1426 (obs/onset (icr:tz-observance-on dt ict:tz-eastern))
1427 (obs (car obs/onset))
1428 (onset (cadr obs/onset))
1429 ;; make sure we get the same result with an absolute time:
1430 (ts (encode-time dt))
1431 (ts-obs/onset (icr:tz-observance-on ts ict:tz-eastern)))
1432 (should (eq 'ical:standard (ical:ast-node-type obs)))
1433 (should (equal start onset))
1434 (should (equal obs/onset ts-obs/onset)))
1435
1436 (let* ((start (ical:make-date-time :year 2007 :month 11 :day 4
1437 :hour 2 :minute 0 :second 0
1438 :zone ict:edt :dst t))
1439 (dt (ical:make-date-time :year 2008 :month 2 :day 1
1440 :hour 12 :minute 30 :second 0
1441 :zone ict:est :dst nil))
1442 (obs/onset (icr:tz-observance-on dt ict:tz-eastern))
1443 (obs (car obs/onset))
1444 (onset (cadr obs/onset))
1445 ;; make sure we get the same result with an absolute time:
1446 (ts (encode-time dt))
1447 (ts-obs/onset (icr:tz-observance-on ts ict:tz-eastern)))
1448 (should (eq 'ical:standard (ical:ast-node-type obs)))
1449 (should (equal start onset))
1450 (should (equal obs/onset ts-obs/onset)))
1451
1452
1453 ;; A date in the middle of the STANDARD observance which is ongoing;
1454 ;; test that the update flag correctly sets the zone information:
1455 (let* ((start (ical:make-date-time :year 2025 :month 11 :day 2
1456 :hour 2 :minute 0 :second 0
1457 :zone ict:edt :dst t))
1458 (dt (ical:make-date-time :year 2026 :month 1 :day 28
1459 :hour 12 :minute 30 :second 0
1460 ;; no zone information
1461 ))
1462 (obs/onset (icr:tz-observance-on dt ict:tz-eastern t))
1463 (obs (car obs/onset))
1464 (onset (cadr obs/onset)))
1465 (should (eq 'ical:standard (ical:ast-node-type obs)))
1466 (should (equal start onset))))
1467
1468
1469;; Tests for recurrence rule interpretation:
1470(cl-defmacro ict:rrule-test (recur-string doc
1471 &key dtstart
1472 (low dtstart)
1473 high
1474 tz
1475 rdates
1476 exdates
1477 members
1478 nonmembers
1479 size
1480 (tags nil)
1481 source)
1482
1483 "Create a test which parses RECUR-STRING to an `icalendar-recur',
1484creates an event with a recurrence set from this value, and checks
1485various properties of the recurrence set.
1486
1487DTSTART should be an `icalendar-date' or `icalendar-date-time'
1488 value appropriate to the RECUR-STRING. The value will be
1489 bound to the symbol `dtstart'; this symbol can thus be used inside
1490 the expressions for MEMBERS and NONMEMBERS.
1491LOW and HIGH should be the bounds of the window in which to compute
1492 recurrences. LOW defaults to DTSTART.
1493TZ, if present, should be an `icalendar-vtimezone'.
1494 Date-times in the recurrence set will be calculated relative to this
1495 time zone.
1496RDATES, if present, should be a list of additional
1497 `icalendar-date' or `icalendar-date-time' values to be added to
1498 the recurrence set *in addition to* those generated by the
1499 recurrence rule (see `icalendar-rdate').
1500EXDATES, if present, should be a list of `icalendar-date' or
1501 `icalendar-date-time' values to be excluded from the recurrence
1502 set, *even if* they are in RDATES or generated by the
1503 recurrence rule (see `icalendar-exdate').
1504MEMBERS, if present, should be a list of values that are expected
1505 to be present in the recurrence set.
1506NONMEMBERS, if present, should be a list of values that are expected
1507 to be excluded from the recurrence set.
1508SIZE, if present, should be a positive integer representing the
1509 expected size of the recurrence set. Defaults to the value of the
1510 COUNT clause in the recurrence rule, if any.
1511TAGS is passed on to `ert-deftest'.
1512SOURCE should be a symbol; it is used to name the test."
1513 `(ert-deftest ,(intern (concat "ict:rrule-test-" (symbol-name source))) ()
1514 ,(format "Parse and evaluate recur-value example from `%s':\n%s"
1515 source doc)
1516 :tags ,tags
1517 (let* ((parsed (ical:parse-from-string 'ical:recur ,recur-string))
1518 (recvalue (ical:ast-node-value parsed))
1519 (until (ical:recur-until recvalue))
1520 (count (ical:recur-count recvalue))
1521 (dtstart ,dtstart)
1522 (tzid
1523 (when (cl-typep dtstart 'ical:date-time)
1524 "America/New_York"))
1525 (recset-size (or ,size count))
1526 (vevent
1527 (ical:make-vevent
1528 (ical:uid (concat "uid-test-" ,(symbol-name source)))
1529 (ical:dtstart dtstart (ical:tzidparam tzid))
1530 (ical:rrule parsed)
1531 (ical:rdate ,rdates)
1532 (ical:exdate ,exdates)))
1533 ;; default for HIGH: UNTIL or DTSTART+3*INTERVAL
1534 (win-high
1535 (or ,high
1536 until
1537 (cadr
1538 (icr:nth-interval 2 ,dtstart recvalue))))
1539 (recs
1540 (if count
1541 (icr:recurrences-to-count vevent ,tz)
1542 (icr:recurrences-in-window ,low win-high vevent ,tz))))
1543 (should (ical:ast-node-valid-p parsed))
1544 (when ,members
1545 (dolist (dt ,members)
1546 (should (member dt recs))))
1547 (when ,nonmembers
1548 (dolist (dt ,nonmembers)
1549 (should-not (member dt recs))))
1550 (when recset-size
1551 (should (length= recs recset-size))))))
1552
1553(ict:rrule-test
1554 "FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1"
1555 "Last non-weekend day of the month"
1556 :dtstart '(3 31 2025)
1557 :high '(6 1 2025)
1558 :members '((3 31 2025) (4 30 2025) (5 30 2025))
1559 :nonmembers '((5 31 2025)) ;; 5/31/2025 is a Saturday
1560 :source rfc5545-sec3.3.10/1)
1561
1562(ict:rrule-test
1563 "FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30"
1564 "Every Sunday in January at 8:30AM and 9:30AM, every other year"
1565 :dtstart (ical:read-date-time "20250105T083000")
1566 :high (ical:read-date-time "20271231T000000")
1567 :members
1568 (let ((jan3-27 (ical:make-date-time :year 2027 :month 1 :day 3
1569 :hour 8 :minute 30 :second 0)))
1570 (list dtstart
1571 ;; 2025: Jan 5, 12, 19, 26
1572 (ical:date-time-variant dtstart :hour 9)
1573 (ical:date-time-variant dtstart :day 12)
1574 (ical:date-time-variant dtstart :day 12 :hour 9)
1575 (ical:date-time-variant dtstart :day 19)
1576 (ical:date-time-variant dtstart :day 19 :hour 9)
1577 (ical:date-time-variant dtstart :day 19)
1578 (ical:date-time-variant dtstart :day 19 :hour 9)
1579 (ical:date-time-variant dtstart :day 26)
1580 (ical:date-time-variant dtstart :day 26 :hour 9)
1581 ;; 2027: Jan 3, 10, 17, 24, 31
1582 (ical:date-time-variant jan3-27 :hour 9)
1583 (ical:date-time-variant jan3-27 :day 10)
1584 (ical:date-time-variant jan3-27 :day 10 :hour 9)
1585 (ical:date-time-variant jan3-27 :day 17)
1586 (ical:date-time-variant jan3-27 :day 17 :hour 9)
1587 (ical:date-time-variant jan3-27 :day 24)
1588 (ical:date-time-variant jan3-27 :day 24 :hour 9)
1589 (ical:date-time-variant jan3-27 :day 31)
1590 (ical:date-time-variant jan3-27 :day 31 :hour 9)))
1591 :nonmembers
1592 (list
1593 (ical:make-date-time :year 2026 :month 1 :day 4
1594 :hour 8 :minute 30 :second 0)
1595 (ical:make-date-time :year 2026 :month 1 :day 4
1596 :hour 9 :minute 30 :second 0))
1597 :source rfc5545-sec3.3.10/2)
1598
1599(ict:rrule-test
1600 "FREQ=YEARLY;BYMONTH=2;BYMONTHDAY=-1"
1601 "Every year on the last day in February"
1602 :dtstart '(2 29 2024)
1603 :high '(3 1 2028)
1604 :members '((2 28 2025) (2 28 2026) (2 28 2027) (2 29 2028))
1605 :nonmembers '((2 28 2028))
1606 :source leap-day/1)
1607
1608(ict:rrule-test
1609 "FREQ=YEARLY;INTERVAL=4;BYMONTH=2;BYMONTHDAY=29"
1610 "Every four years on February 29"
1611 :dtstart '(2 29 2024)
1612 :high '(3 1 2028)
1613 :members '((2 29 2028))
1614 :nonmembers '((2 28 2028))
1615 :source leap-day/2)
1616
1617(ict:rrule-test
1618"FREQ=DAILY;COUNT=10"
1619"Daily for 10 occurrences"
1620:dtstart (ical:make-date-time :year 1997 :month 9 :day 2
1621 :hour 9 :minute 0 :second 0)
1622:members
1623;; (1997 9:00 AM EDT) September 2-11
1624(mapcar
1625 (lambda (day) (ical:date-time-variant dtstart :day day))
1626 (number-sequence 2 11))
1627:source rfc5545-sec3.3.10/3)
1628
1629(ict:rrule-test
1630 "RRULE:FREQ=YEARLY"
1631 "Every year on a specific date, e.g. an anniversary"
1632 :dtstart '(11 11 2024)
1633 :high '(10 1 2030)
1634 :members '((11 11 2024)
1635 (11 11 2025)
1636 (11 11 2026)
1637 (11 11 2027)
1638 (11 11 2028)
1639 (11 11 2029))
1640 :nonmembers '((11 11 2030))
1641 :source rfc5545-sec3.6.1/3)
1642
1643;; Time zone tests
1644
1645(ict:rrule-test
1646 "RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=-1SU;UNTIL=19730429T070000Z"
1647 "Every year on the last Sunday of April (through 1973-04-29) at 2AM.
1648(Onset of US Eastern Daylight Time.)"
1649 :tz ict:tz-eastern
1650 ;; DTSTART and all the times below are at *3*AM EDT, because 2AM EST
1651 ;; (the onset of the observance) does not exist as a local time:
1652 :dtstart (ical:make-date-time :year 1967 :month 4 :day 30
1653 :hour 3 :minute 0 :second 0
1654 :zone ict:edt :dst t)
1655 :high (ical:date-time-variant dtstart :year 1973 :month 4 :day 30
1656 :zone ict:edt :dst t)
1657 :members
1658 (list
1659 (ical:date-time-variant dtstart :year 1968 :day 28 :tz 'preserve)
1660 (ical:date-time-variant dtstart :year 1969 :day 27 :tz 'preserve)
1661 (ical:date-time-variant dtstart :year 1970 :day 26 :tz 'preserve)
1662 (ical:date-time-variant dtstart :year 1971 :day 25 :tz 'preserve)
1663 (ical:date-time-variant dtstart :year 1972 :day 30 :tz 'preserve)
1664 (ical:date-time-variant dtstart :year 1973 :day 29 :tz 'preserve))
1665 :source rfc5545-sec3.6.5/1)
1666
1667(ict:rrule-test
1668 "RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU"
1669 "Every year on the first Sunday of November at 2AM.
1670(Onset of Eastern Standard Time)."
1671 :tz ict:tz-eastern
1672 :dtstart (ical:make-date-time :year 2007 :month 11 :day 4
1673 :hour 2 :minute 0 :second 0
1674 :zone ict:edt :dst t)
1675 :high (ical:date-time-variant dtstart :year 2010 :month 11 :day 8
1676 :zone ict:est :dst nil)
1677 :members
1678 ;; all the times below are at *1*AM EST, because 2AM EDT (the onset of
1679 ;; the observance) is when clocks get set back:
1680 (list (ical:date-time-variant dtstart
1681 :year 2008 :month 11 :day 2
1682 :zone ict:est :dst nil)
1683 (ical:date-time-variant dtstart
1684 :year 2009 :month 11 :day 1
1685 :zone ict:est :dst nil)
1686 (ical:date-time-variant dtstart
1687 :year 2010 :month 11 :day 7
1688 :zone ict:est :dst nil))
1689 :source rfc5545-sec3.6.5/3.1)
1690
1691(ict:rrule-test
1692 "RRULE:FREQ=MONTHLY;INTERVAL=3;BYDAY=1SU"
1693 "Every three months on the first Sunday of the month."
1694 :dtstart '(1 5 2025)
1695 :high '(1 1 2026)
1696 :members (list '(4 6 2025)
1697 '(7 6 2025)
1698 '(10 5 2025))
1699 :nonmembers (list '(1 12 2025) ;; second Sun.
1700 '(2 2 2025) ;; first Sun. in Feb.
1701 '(4 5 2025)) ;; Sat.
1702 :source monthly/interval)
1703
1704(ict:rrule-test
1705 "RRULE:FREQ=DAILY;COUNT=10\n"
1706 "Daily for 10 occurrences"
1707 :dtstart (ical:read-date-time "19970902T090000")
1708 :members
1709 (mapcar
1710 (lambda (day) (ical:date-time-variant dtstart :day day))
1711 (number-sequence 2 11))
1712 :nonmembers (list (ical:date-time-variant dtstart :day 12))
1713 :high (ical:read-date-time "19970912T090000")
1714 :source rfc5545-sec3.8.5.3/1)
1715
1716(ict:rrule-test
1717 "RRULE:FREQ=DAILY;UNTIL=19971224T000000Z\n"
1718 "Daily at 9AM until December 24, 1997"
1719 :tags '(:expensive-test)
1720 :tz ict:tz-eastern
1721 :dtstart (ical:make-date-time :year 1997 :month 9 :day 2
1722 :hour 9 :minute 0 :second 0
1723 :zone ict:edt :dst t)
1724 :members
1725 (append
1726 ;; EDT:
1727 (mapcar
1728 (lambda (day) (ical:date-time-variant dtstart :day day :tz 'preserve))
1729 (number-sequence 2 30)) ;; Sept. 2--30
1730 (mapcar
1731 (lambda (day) (ical:date-time-variant dtstart :month 10 :day day
1732 :tz 'preserve))
1733 (number-sequence 1 25)) ;; Oct. 1--25
1734 ;; EST:
1735 (mapcar
1736 (lambda (day)
1737 (ical:date-time-variant dtstart :month 10 :day day :zone ict:est :dst nil))
1738 (number-sequence 26 31))) ;; Oct. 26--31
1739 :source rfc5545-sec3.8.5.3/2)
1740
1741(ict:rrule-test
1742 "RRULE:FREQ=DAILY;INTERVAL=2\n"
1743 "Every other day - forever"
1744 :tz ict:tz-eastern
1745 :dtstart (ical:make-date-time :year 1997 :month 9 :day 2
1746 :hour 9 :minute 0 :second 0
1747 :zone ict:edt :dst t)
1748 :high (ical:make-date-time :year 1997 :month 12 :day 4
1749 :hour 0 :minute 0 :second 0
1750 :zone ict:est :dst nil)
1751 :members
1752 (append
1753 ;; (1997 9:00 AM EDT) September 2,4,6,8...24,26,28,30;
1754 ;; October 2,4,6...20,22,24
1755 (mapcar
1756 (lambda (n)
1757 (ical:date-time-variant dtstart :day (* 2 n) :tz 'preserve))
1758 (number-sequence 1 15))
1759 (mapcar
1760 (lambda (n)
1761 (ical:date-time-variant dtstart :month 10 :day (* 2 n) :tz 'preserve))
1762 (number-sequence 1 12))
1763 ;; (1997 9:00 AM EST) October 26,28,30;
1764 ;; November 1,3,5,7...25,27,29;
1765 ;; December 1,3,...
1766 (mapcar
1767 (lambda (n)
1768 (ical:date-time-variant dtstart :month 10 :day (* 2 n)
1769 :zone ict:est :dst nil))
1770 (number-sequence 13 15))
1771 (mapcar
1772 (lambda (n)
1773 (ical:date-time-variant dtstart :month 11 :day (1- (* 2 n))
1774 :zone ict:est :dst nil))
1775 (number-sequence 1 15))
1776 (mapcar
1777 (lambda (n)
1778 (ical:date-time-variant dtstart :month 12 :day (1- (* 2 n))
1779 :zone ict:est :dst nil))
1780 (number-sequence 1 2)))
1781
1782 :nonmembers
1783 (list
1784 ;; e.g.
1785 (ical:make-date-time :year 1997 :month 10 :day 27
1786 :hour 9 :minute 0 :second 0
1787 :zone ict:est :dst nil))
1788 :source rfc5545-sec3.8.5.3/3)
1789
1790(ict:rrule-test
1791 "RRULE:FREQ=DAILY;INTERVAL=10;COUNT=5\n"
1792 "Every ten days for five recurrences"
1793 :tz ict:tz-eastern
1794 :dtstart (ical:make-date-time :year 1997 :month 9 :day 2
1795 :hour 9 :minute 0 :second 0
1796 :zone ict:edt :dst t)
1797
1798 :members ;; (1997 9:00 AM EDT) September 2,12,22; October 2,12
1799 (list
1800 dtstart
1801 (ical:make-date-time :year 1997 :month 9 :day 12
1802 :hour 9 :minute 0 :second 0
1803 :zone ict:edt :dst t)
1804 (ical:make-date-time :year 1997 :month 9 :day 22
1805 :hour 9 :minute 0 :second 0
1806 :zone ict:edt :dst t)
1807 (ical:make-date-time :year 1997 :month 10 :day 2
1808 :hour 9 :minute 0 :second 0
1809 :zone ict:edt :dst t)
1810 (ical:make-date-time :year 1997 :month 10 :day 12
1811 :hour 9 :minute 0 :second 0
1812 :zone ict:edt :dst t))
1813 :source rfc5545-sec3.8.5.3/4)
1814
1815(ict:rrule-test
1816 "RRULE:FREQ=YEARLY;UNTIL=20000131T140000Z;BYMONTH=1;BYDAY=SU,MO,TU,WE,TH,FR,SA\n"
1817 "Every day in January, for three years (weekdays explicit)"
1818 :tags '(:expensive-test)
1819 :tz ict:tz-eastern
1820 :dtstart (ical:make-date-time :year 1998 :month 1 :day 1
1821 :hour 9 :minute 0 :second 0
1822 :zone ict:est :dst nil)
1823 :high (ical:make-date-time :year 2000 :month 2 :day 1
1824 :hour 9 :minute 0 :second 0
1825 :zone ict:est :dst nil)
1826 :members
1827 ;; (1998 9:00 AM EST)January 1-31
1828 ;; (1999 9:00 AM EST)January 1-31
1829 ;; (2000 9:00 AM EST)January 1-31
1830 (append
1831 (mapcar
1832 (lambda (day) (ical:date-time-variant dtstart :day day :tz 'preserve))
1833 (number-sequence 1 31))
1834 (mapcar
1835 (lambda (day)
1836 (ical:date-time-variant dtstart :year 1999 :day day :tz 'preserve))
1837 (number-sequence 1 31))
1838 (mapcar
1839 (lambda (day)
1840 (ical:date-time-variant dtstart :year 2000 :day day :tz 'preserve))
1841 (number-sequence 1 31)))
1842 :source rfc5545-sec3.8.5.3/5)
1843
1844(ict:rrule-test
1845 "RRULE:FREQ=DAILY;UNTIL=20000131T140000Z;BYMONTH=1\n"
1846 "Every day in January, for three years (weekdays implicit)"
1847 :tags '(:expensive-test)
1848 ;; TODO: as things are currently implemented, this way of expressing
1849 ;; the rule is quite expensive, since we end up computing intervals and
1850 ;; recurrences for every day of the year, even though the only relevant
1851 ;; days are in January and there are no recurrences on the other days.
1852 ;; We could try to optimize e.g. icr:refine-from-clauses to deal with such
1853 ;; cases.
1854 :tz ict:tz-eastern
1855 :dtstart (ical:make-date-time :year 1998 :month 1 :day 1
1856 :hour 9 :minute 0 :second 0
1857 :zone ict:est :dst nil)
1858 :high (ical:make-date-time :year 2000 :month 2 :day 1
1859 :hour 9 :minute 0 :second 0
1860 :zone ict:est :dst nil)
1861 :members
1862 ;; (1998 9:00 AM EST)January 1-31
1863 ;; (1999 9:00 AM EST)January 1-31
1864 ;; (2000 9:00 AM EST)January 1-31
1865 (append
1866 (mapcar
1867 (lambda (day) (ical:date-time-variant dtstart :day day :tz 'preserve))
1868 (number-sequence 1 31))
1869 (mapcar
1870 (lambda (day)
1871 (ical:date-time-variant dtstart :year 1999 :day day :tz 'preserve))
1872 (number-sequence 1 31))
1873 (mapcar
1874 (lambda (day)
1875 (ical:date-time-variant dtstart :year 2000 :day day :tz 'preserve))
1876 (number-sequence 1 31)))
1877 :source rfc5545-sec3.8.5.3/6)
1878
1879(ict:rrule-test
1880 "RRULE:FREQ=WEEKLY;COUNT=10\n"
1881 "Weekly for ten occurrences"
1882 :tz ict:tz-eastern
1883 :dtstart (ical:make-date-time :year 1997 :month 9 :day 2
1884 :hour 9 :minute 0 :second 0
1885 :zone ict:edt :dst t)
1886 :members
1887 (append
1888 ;; (1997 9:00 AM EDT) September 2,9,16,23,30;October 7,14,21
1889 (mapcar
1890 (lambda (day)
1891 (ical:date-time-variant dtstart :day day :tz 'preserve))
1892 (list 2 9 16 23 30))
1893 (mapcar
1894 (lambda (day)
1895 (ical:date-time-variant dtstart :month 10 :day day :tz 'preserve))
1896 (list 7 14 21))
1897 ;; (1997 9:00 AM EST) October 28;November 4
1898 (list
1899 (ical:make-date-time :year 1997 :month 10 :day 28
1900 :hour 9 :minute 0 :second 0
1901 :zone ict:est :dst nil)
1902 (ical:make-date-time :year 1997 :month 11 :day 4
1903 :hour 9 :minute 0 :second 0
1904 :zone ict:est :dst nil)))
1905 :source rfc5545-sec3.8.5.3/7)
1906
1907(ict:rrule-test
1908 "RRULE:FREQ=WEEKLY;UNTIL=19971224T000000Z\n"
1909 "Every week until December 24, 1997"
1910 :tz ict:tz-eastern
1911 :dtstart (ical:make-date-time :year 1997 :month 9 :day 2
1912 :hour 9 :minute 0 :second 0
1913 :zone ict:edt :dst t)
1914 :members
1915 (let ((oct97 (ical:date-time-variant dtstart :month 10
1916 :zone ict:edt :dst t))
1917 (nov97 (ical:date-time-variant dtstart :month 11
1918 :zone ict:est :dst nil))
1919 (dec97 (ical:date-time-variant dtstart :month 12
1920 :zone ict:est :dst nil)))
1921 (append
1922 ;; (1997 9:00 AM EDT) September 2,9,16,23,30;
1923 ;; October 7,14,21
1924 (mapcar
1925 (lambda (day)
1926 (ical:date-time-variant dtstart :day day :tz 'preserve))
1927 (list 2 9 16 23 30))
1928 (mapcar
1929 (lambda (day)
1930 (ical:date-time-variant oct97 :day day :tz 'preserve))
1931 (list 7 14 21))
1932 ;; (1997 9:00 AM EST) October 28;
1933 ;; November 4,11,18,25;
1934 ;; December 2,9,16,23
1935 (list (ical:date-time-variant oct97 :day 28 :zone ict:est :dst nil))
1936 (mapcar
1937 (lambda (day)
1938 (ical:date-time-variant nov97 :day day :tz 'preserve))
1939 (list 4 11 18 25))
1940 (mapcar
1941 (lambda (day)
1942 (ical:date-time-variant dec97 :day day :tz 'preserve))
1943 (list 2 9 16 23))))
1944 :source rfc5545-sec3.8.5.3/8)
1945
1946(ict:rrule-test
1947 "RRULE:FREQ=WEEKLY;INTERVAL=2;WKST=SU\n"
1948 "Every other week - forever; Weekstart on Sunday"
1949 :tz ict:tz-eastern
1950 :dtstart (ical:make-date-time :year 1997 :month 9 :day 2
1951 :hour 9 :minute 0 :second 0
1952 :zone ict:edt :dst t)
1953 :high (ical:make-date-time :year 1998 :month 3 :day 1
1954 :hour 9 :minute 0 :second 0
1955 :zone ict:est :dst nil)
1956 :members
1957 (list
1958 ;; ==> (1997 9:00 AM EDT) September 2,16,30;
1959 ;; October 14
1960 dtstart
1961 (ical:date-time-variant dtstart :day 16 :tz 'preserve)
1962 (ical:date-time-variant dtstart :day 30 :tz 'preserve)
1963 (ical:date-time-variant dtstart :month 10 :day 14 :tz 'preserve)
1964 ;; (1997 9:00 AM EST) October 28;
1965 ;; November 11,25;
1966 ;; December 9,23
1967 (ical:date-time-variant dtstart :month 10 :day 28 :zone ict:est :dst nil)
1968 (ical:date-time-variant dtstart :month 11 :day 11 :zone ict:est :dst nil)
1969 (ical:date-time-variant dtstart :month 11 :day 25 :zone ict:est :dst nil)
1970 (ical:date-time-variant dtstart :month 12 :day 23 :zone ict:est :dst nil)
1971 ;; (1998 9:00 AM EST) January 6,20;
1972 ;; February 3, 17
1973 (ical:date-time-variant dtstart :year 1998 :month 1 :day 6
1974 :zone ict:est :dst nil)
1975 (ical:date-time-variant dtstart :year 1998 :month 1 :day 20
1976 :zone ict:est :dst nil)
1977 (ical:date-time-variant dtstart :year 1998 :month 2 :day 3
1978 :zone ict:est :dst nil)
1979 (ical:date-time-variant dtstart :year 1998 :month 2 :day 17
1980 :zone ict:est :dst nil))
1981 :source rfc5545-sec3.8.5.3/9)
1982
1983(ict:rrule-test
1984"RRULE:FREQ=WEEKLY;UNTIL=19971007T000000Z;WKST=SU;BYDAY=TU,TH\n"
1985"Weekly on Tuesday and Thursday for five weeks, using UNTIL"
1986 :tz ict:tz-eastern
1987 :dtstart (ical:make-date-time :year 1997 :month 9 :day 2
1988 :hour 9 :minute 0 :second 0
1989 :zone ict:edt :dst t)
1990 :high (ical:make-date-time :year 1997 :month 10 :day 8
1991 :hour 0 :minute 0 :second 0 :zone 0)
1992 :members
1993 (list
1994 ;; ==> (1997 9:00 AM EDT) September 2,4,9,11,16,18,23,25,30;
1995 ;; October 2
1996 dtstart
1997 (ical:date-time-variant dtstart :day 4 :tz 'preserve)
1998 (ical:date-time-variant dtstart :day 9 :tz 'preserve)
1999 (ical:date-time-variant dtstart :day 11 :tz 'preserve)
2000 (ical:date-time-variant dtstart :day 16 :tz 'preserve)
2001 (ical:date-time-variant dtstart :day 18 :tz 'preserve)
2002 (ical:date-time-variant dtstart :day 23 :tz 'preserve)
2003 (ical:date-time-variant dtstart :day 25 :tz 'preserve)
2004 (ical:date-time-variant dtstart :day 30 :tz 'preserve)
2005 (ical:date-time-variant dtstart :month 10 :day 2 :tz 'preserve))
2006 :source rfc5545-sec3.8.5.3/10)
2007
2008(ict:rrule-test
2009"RRULE:FREQ=WEEKLY;COUNT=10;WKST=SU;BYDAY=TU,TH\n"
2010"Weekly on Tuesday and Thursday for five weeks, using COUNT"
2011 :tz ict:tz-eastern
2012 :dtstart (ical:make-date-time :year 1997 :month 9 :day 2
2013 :hour 9 :minute 0 :second 0
2014 :zone ict:edt :dst t)
2015 :high (ical:make-date-time :year 1997 :month 10 :day 8
2016 :hour 0 :minute 0 :second 0 :zone 0)
2017 :members
2018 (list
2019 ;; ==> (1997 9:00 AM EDT) September 2,4,9,11,16,18,23,25,30;
2020 ;; October 2
2021 dtstart
2022 (ical:date-time-variant dtstart :day 4 :tz 'preserve)
2023 (ical:date-time-variant dtstart :day 9 :tz 'preserve)
2024 (ical:date-time-variant dtstart :day 11 :tz 'preserve)
2025 (ical:date-time-variant dtstart :day 16 :tz 'preserve)
2026 (ical:date-time-variant dtstart :day 18 :tz 'preserve)
2027 (ical:date-time-variant dtstart :day 23 :tz 'preserve)
2028 (ical:date-time-variant dtstart :day 25 :tz 'preserve)
2029 (ical:date-time-variant dtstart :day 30 :tz 'preserve)
2030 (ical:date-time-variant dtstart :month 10 :day 2 :tz 'preserve))
2031 :source rfc5545-sec3.8.5.3/11)
2032
2033(ict:rrule-test
2034 "RRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR\n"
2035 "Every other week on Monday, Wednesday, and Friday until December 24,
20361997, starting on Monday, September 1, 1997"
2037 :tz ict:tz-eastern
2038 :dtstart (ical:make-date-time :year 1997 :month 9 :day 1
2039 :hour 9 :minute 0 :second 0
2040 :zone ict:edt :dst t)
2041 :members
2042 (list
2043 dtstart
2044 ;; ==> (1997 9:00 AM EDT) September 1,3,5,15,17,19,29;
2045 ;; October 1,3,13,15,17
2046 (ical:date-time-variant dtstart :day 3 :tz 'preserve)
2047 (ical:date-time-variant dtstart :day 5 :tz 'preserve)
2048 (ical:date-time-variant dtstart :day 15 :tz 'preserve)
2049 (ical:date-time-variant dtstart :day 17 :tz 'preserve)
2050 (ical:date-time-variant dtstart :day 19 :tz 'preserve)
2051 (ical:date-time-variant dtstart :day 29 :tz 'preserve)
2052 (ical:date-time-variant dtstart :month 10 :day 1 :tz 'preserve)
2053 (ical:date-time-variant dtstart :month 10 :day 3 :tz 'preserve)
2054 (ical:date-time-variant dtstart :month 10 :day 13 :tz 'preserve)
2055 (ical:date-time-variant dtstart :month 10 :day 15 :tz 'preserve)
2056 (ical:date-time-variant dtstart :month 10 :day 17 :tz 'preserve)
2057 ;; (1997 9:00 AM EST) October 27,29,31;
2058 ;; November 10,12,14,24,26,28;
2059 ;; December 8,10,12,22
2060 (ical:date-time-variant dtstart :month 10 :day 27 :zone ict:est :dst nil)
2061 (ical:date-time-variant dtstart :month 10 :day 29 :zone ict:est :dst nil)
2062 (ical:date-time-variant dtstart :month 10 :day 31 :zone ict:est :dst nil)
2063 (ical:date-time-variant dtstart :month 11 :day 10 :zone ict:est :dst nil)
2064 (ical:date-time-variant dtstart :month 11 :day 12 :zone ict:est :dst nil)
2065 (ical:date-time-variant dtstart :month 11 :day 14 :zone ict:est :dst nil)
2066 (ical:date-time-variant dtstart :month 11 :day 24 :zone ict:est :dst nil)
2067 (ical:date-time-variant dtstart :month 11 :day 26 :zone ict:est :dst nil)
2068 (ical:date-time-variant dtstart :month 11 :day 28 :zone ict:est :dst nil)
2069 (ical:date-time-variant dtstart :month 12 :day 8 :zone ict:est :dst nil)
2070 (ical:date-time-variant dtstart :month 12 :day 10 :zone ict:est :dst nil)
2071 (ical:date-time-variant dtstart :month 12 :day 12 :zone ict:est :dst nil)
2072 (ical:date-time-variant dtstart :month 12 :day 22 :zone ict:est :dst nil))
2073 :nonmembers
2074 (list
2075 ;; These match the rule, but are just past the UNTIL date:
2076 (ical:date-time-variant dtstart :month 12 :day 24 :zone ict:est :dst nil)
2077 (ical:date-time-variant dtstart :month 12 :day 26 :zone ict:est :dst nil))
2078 :source rfc5545-sec3.8.5.3/12)
2079
2080(ict:rrule-test
2081 "RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=8;WKST=SU;BYDAY=TU,TH\n"
2082 "Every other week on Tuesday and Thursday, for 8 occurrences"
2083 :tz ict:tz-eastern
2084 :dtstart (ical:make-date-time :year 1997 :month 9 :day 2
2085 :hour 9 :minute 0 :second 0
2086 :zone ict:edt :dst t)
2087 :members
2088 ;; ==> (1997 9:00 AM EDT) September 2,4,16,18,30;
2089 ;; October 2,14,16
2090 (list
2091 dtstart
2092 (ical:date-time-variant dtstart :day 4 :tz 'preserve)
2093 (ical:date-time-variant dtstart :day 16 :tz 'preserve)
2094 (ical:date-time-variant dtstart :day 18 :tz 'preserve)
2095 (ical:date-time-variant dtstart :day 30 :tz 'preserve)
2096 (ical:date-time-variant dtstart :month 10 :day 2 :tz 'preserve)
2097 (ical:date-time-variant dtstart :month 10 :day 14 :tz 'preserve)
2098 (ical:date-time-variant dtstart :month 10 :day 16 :tz 'preserve))
2099 :source rfc5545-sec3.8.5.3/13)
2100
2101(ict:rrule-test
2102 "RRULE:FREQ=MONTHLY;COUNT=10;BYDAY=1FR\n"
2103 "Monthly on the first Friday for 10 occurrences"
2104 :tz ict:tz-eastern
2105 :dtstart (ical:make-date-time :year 1997 :month 9 :day 5
2106 :hour 9 :minute 0 :second 0
2107 :zone ict:edt :dst t)
2108 :members
2109 (list
2110 ;; ==> (1997 9:00 AM EDT) September 5;October 3
2111 dtstart
2112 (ical:date-time-variant dtstart :month 10 :day 3 :tz 'preserve)
2113 ;; (1997 9:00 AM EST) November 7;December 5
2114 (ical:date-time-variant dtstart :month 11 :day 7 :zone ict:est :dst nil)
2115 (ical:date-time-variant dtstart :month 12 :day 5 :zone ict:est :dst nil)
2116 ;; (1998 9:00 AM EST) January 2;February 6;March 6;April 3
2117 (ical:date-time-variant dtstart :year 1998 :month 1 :day 2
2118 :zone ict:est :dst nil)
2119 (ical:date-time-variant dtstart :year 1998 :month 2 :day 6
2120 :zone ict:est :dst nil)
2121 (ical:date-time-variant dtstart :year 1998 :month 3 :day 6
2122 :zone ict:est :dst nil)
2123 (ical:date-time-variant dtstart :year 1998 :month 4 :day 3
2124 :zone ict:est :dst nil)
2125 ;; (1998 9:00 AM EDT) May 1;June 5
2126 (ical:date-time-variant dtstart :year 1998 :month 5 :day 1 :tz 'preserve)
2127 (ical:date-time-variant dtstart :year 1998 :month 6 :day 5 :tz 'preserve))
2128 :source rfc5545-sec3.8.5.3/14)
2129
2130(ict:rrule-test
2131 "RRULE:FREQ=MONTHLY;UNTIL=19971224T000000Z;BYDAY=1FR\n"
2132 "Monthly on the first Friday until December 24, 1997"
2133 :tz ict:tz-eastern
2134 :dtstart (ical:make-date-time :year 1997 :month 9 :day 5
2135 :hour 9 :minute 0 :second 0
2136 :zone ict:edt :dst t)
2137 :members
2138 (list
2139 ;; ==> (1997 9:00 AM EDT) September 5; October 3
2140 dtstart
2141 (ical:date-time-variant dtstart :month 10 :day 3 :tz 'preserve)
2142 ;; (1997 9:00 AM EST) November 7;December 5
2143 (ical:date-time-variant dtstart :month 11 :day 7 :zone ict:est :dst nil)
2144 (ical:date-time-variant dtstart :month 12 :day 5 :zone ict:est :dst nil))
2145 :source rfc5545-sec3.8.5.3/15)
2146
2147(ict:rrule-test
2148 "RRULE:FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=1SU,-1SU\n"
2149 "Every other month on the first and last Sunday of the month for 10 occurrences"
2150 :tz ict:tz-eastern
2151 :dtstart (ical:make-date-time :year 1997 :month 9 :day 7
2152 :hour 9 :minute 0 :second 0
2153 :zone ict:edt :dst t)
2154 :members
2155 (list
2156 ;; ==> (1997 9:00 AM EDT) September 7,28
2157 dtstart
2158 (ical:date-time-variant dtstart :day 28 :tz 'preserve)
2159 ;; (1997 9:00 AM EST) November 2,30
2160 (ical:date-time-variant dtstart :month 11 :day 2 :zone ict:est :dst nil)
2161 (ical:date-time-variant dtstart :month 11 :day 30 :zone ict:est :dst nil)
2162 ;; (1998 9:00 AM EST) January 4,25;March 1,29
2163 (ical:date-time-variant dtstart :year 1998 :month 1 :day 4
2164 :zone ict:est :dst nil)
2165 (ical:date-time-variant dtstart :year 1998 :month 1 :day 25
2166 :zone ict:est :dst nil)
2167 (ical:date-time-variant dtstart :year 1998 :month 3 :day 1
2168 :zone ict:est :dst nil)
2169 (ical:date-time-variant dtstart :year 1998 :month 3 :day 29
2170 :zone ict:est :dst nil)
2171 ;; (1998 9:00 AM EDT) May 3,31
2172 (ical:date-time-variant dtstart :year 1998 :month 5 :day 3 :tz 'preserve)
2173 (ical:date-time-variant dtstart :year 1998 :month 5 :day 31 :tz 'preserve))
2174 :source rfc5545-sec3.8.5.3/16)
2175
2176(ict:rrule-test
2177 "RRULE:FREQ=MONTHLY;COUNT=6;BYDAY=-2MO\n"
2178 "Monthly on the second-to-last Monday of the month for 6 months"
2179 :tz ict:tz-eastern
2180 :dtstart (ical:make-date-time :year 1997 :month 9 :day 22
2181 :hour 9 :minute 0 :second 0
2182 :zone ict:edt :dst t)
2183 :members
2184 (list
2185 ;; ==> (1997 9:00 AM EDT) September 22;October 20
2186 dtstart
2187 (ical:date-time-variant dtstart :month 10 :day 20 :tz 'preserve)
2188 ;; (1997 9:00 AM EST) November 17;December 22
2189 (ical:date-time-variant dtstart :month 11 :day 17
2190 :zone ict:est :dst nil)
2191 (ical:date-time-variant dtstart :month 12 :day 22
2192 :zone ict:est :dst nil)
2193 ;; (1998 9:00 AM EST) January 19;February 16
2194 (ical:date-time-variant dtstart :year 1998 :month 1 :day 19
2195 :zone ict:est :dst nil)
2196 (ical:date-time-variant dtstart :year 1998 :month 2 :day 16
2197 :zone ict:est :dst nil))
2198 :source rfc5545-sec3.8.5.3/17)
2199
2200(ict:rrule-test
2201 "RRULE:FREQ=MONTHLY;BYMONTHDAY=-3\n"
2202 "Monthly on the third-to-the-last day of the month, forever"
2203 :tz ict:tz-eastern
2204 :dtstart (ical:make-date-time :year 1997 :month 9 :day 28
2205 :hour 9 :minute 0 :second 0
2206 :zone ict:edt :dst t)
2207 :high (ical:make-date-time :year 1998 :month 3 :day 1
2208 :hour 0 :minute 0 :second 0
2209 :zone ict:est :dst nil)
2210 :members
2211 (list
2212 ;; ==> (1997 9:00 AM EDT) September 28
2213 dtstart
2214 ;; (1997 9:00 AM EST) October 29;November 28;December 29
2215 (ical:date-time-variant dtstart :month 10 :day 29
2216 :zone ict:est :dst nil)
2217 (ical:date-time-variant dtstart :month 11 :day 28
2218 :zone ict:est :dst nil)
2219 (ical:date-time-variant dtstart :month 12 :day 29
2220 :zone ict:est :dst nil)
2221 ;; (1998 9:00 AM EST) January 29;February 26
2222 (ical:date-time-variant dtstart :year 1998 :month 1 :day 29
2223 :zone ict:est :dst nil)
2224 (ical:date-time-variant dtstart :year 1998 :month 2 :day 26
2225 :zone ict:est :dst nil))
2226 :source rfc5545-sec3.8.5.3/18)
2227
2228(ict:rrule-test
2229 "RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=2,15\n"
2230 "Monthly on the 2nd and 15th of the month for 10 occurrences"
2231 :tz ict:tz-eastern
2232 :dtstart (ical:make-date-time :year 1997 :month 9 :day 2
2233 :hour 9 :minute 0 :second 0
2234 :zone ict:edt :dst t)
2235 :members
2236 (list
2237 ;; ==> (1997 9:00 AM EDT) September 2,15;October 2,15
2238 dtstart
2239 (ical:date-time-variant dtstart :day 15 :tz 'preserve)
2240 (ical:date-time-variant dtstart :month 10 :day 2 :tz 'preserve)
2241 (ical:date-time-variant dtstart :month 10 :day 15 :tz 'preserve)
2242 ;; (1997 9:00 AM EST) November 2,15;December 2,15
2243 (ical:date-time-variant dtstart :month 11 :day 2
2244 :zone ict:est :dst nil)
2245 (ical:date-time-variant dtstart :month 11 :day 15
2246 :zone ict:est :dst nil)
2247 (ical:date-time-variant dtstart :month 12 :day 2
2248 :zone ict:est :dst nil)
2249 (ical:date-time-variant dtstart :month 12 :day 15
2250 :zone ict:est :dst nil)
2251 ;; (1998 9:00 AM EST) January 2,15
2252 (ical:date-time-variant dtstart :year 1998 :month 1 :day 2
2253 :zone ict:est :dst nil)
2254 (ical:date-time-variant dtstart :year 1998 :month 1 :day 15
2255 :zone ict:est :dst nil))
2256 :source rfc5545-sec3.8.5.3/19)
2257
2258(ict:rrule-test
2259 "RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=1,-1\n"
2260 "Monthly on the first and last day of the month for 10 occurrences"
2261 :tz ict:tz-eastern
2262 :dtstart (ical:make-date-time :year 1997 :month 9 :day 30
2263 :hour 9 :minute 0 :second 0
2264 :zone ict:edt :dst t)
2265 :members
2266 (list
2267 ;; ==> (1997 9:00 AM EDT) September 30;October 1
2268 dtstart
2269 (ical:date-time-variant dtstart :month 10 :day 1 :tz 'preserve)
2270 ;; (1997 9:00 AM EST) October 31;November 1,30;December 1,31
2271 (ical:date-time-variant dtstart :month 10 :day 31
2272 :zone ict:est :dst nil)
2273 (ical:date-time-variant dtstart :month 11 :day 1
2274 :zone ict:est :dst nil)
2275 (ical:date-time-variant dtstart :month 11 :day 30
2276 :zone ict:est :dst nil)
2277 (ical:date-time-variant dtstart :month 12 :day 1
2278 :zone ict:est :dst nil)
2279 (ical:date-time-variant dtstart :month 12 :day 31
2280 :zone ict:est :dst nil)
2281 ;; (1998 9:00 AM EST) January 1,31;February 1
2282 (ical:date-time-variant dtstart :year 1998 :month 1 :day 1
2283 :zone ict:est :dst nil)
2284 (ical:date-time-variant dtstart :year 1998 :month 1 :day 31
2285 :zone ict:est :dst nil)
2286 (ical:date-time-variant dtstart :year 1998 :month 2 :day 1
2287 :zone ict:est :dst nil))
2288 :source rfc5545-sec3.8.5.3/20)
2289
2290(ict:rrule-test
2291 "RRULE:FREQ=MONTHLY;INTERVAL=18;COUNT=10;BYMONTHDAY=10,11,12,13,14,15\n"
2292 "Every 18 months on the 10th thru 15th of the month for 10 occurrences"
2293 :tz ict:tz-eastern
2294 :dtstart (ical:make-date-time :year 1997 :month 9 :day 10
2295 :hour 9 :minute 0 :second 0
2296 :zone ict:edt :dst t)
2297 :members
2298 (append
2299 (list
2300 ;; ==> (1997 9:00 AM EDT) September 10,11,12,13,14,15
2301 dtstart
2302 (ical:date-time-variant dtstart :day 11 :tz 'preserve)
2303 (ical:date-time-variant dtstart :day 12 :tz 'preserve)
2304 (ical:date-time-variant dtstart :day 13 :tz 'preserve)
2305 (ical:date-time-variant dtstart :day 14 :tz 'preserve)
2306 (ical:date-time-variant dtstart :day 15 :tz 'preserve))
2307
2308 ;; (1999 9:00 AM EST) March 10,11,12,13
2309 (let ((mar99 (ical:make-date-time :year 1999 :month 3 :day 10
2310 :hour 9 :minute 0 :second 0
2311 :zone ict:est :dst nil)))
2312 (list
2313 mar99
2314 (ical:date-time-variant mar99 :day 11 :tz 'preserve)
2315 (ical:date-time-variant mar99 :day 12 :tz 'preserve)
2316 (ical:date-time-variant mar99 :day 13 :tz 'preserve))))
2317 :nonmembers
2318 (list
2319 ;; These match the rule but are excluded by the COUNT clause:
2320 (ical:make-date-time :year 1999 :month 3 :day 14
2321 :hour 9 :minute 0 :second 0
2322 :zone ict:est :dst nil)
2323 (ical:make-date-time :year 1999 :month 3 :day 15
2324 :hour 9 :minute 0 :second 0
2325 :zone ict:est :dst nil))
2326 :source rfc5545-sec3.8.5.3/21)
2327
2328(ict:rrule-test
2329 "RRULE:FREQ=MONTHLY;INTERVAL=2;BYDAY=TU\n"
2330 "Every Tuesday, every other month"
2331 :tz ict:tz-eastern
2332 :dtstart (ical:make-date-time :year 1997 :month 9 :day 2
2333 :hour 9 :minute 0 :second 0
2334 :zone ict:edt :dst t)
2335 :high (ical:make-date-time :year 1998 :month 4 :day 1
2336 :hour 0 :minute 0 :second 0
2337 :zone ict:est :dst nil)
2338 :members
2339 (list
2340 ;; ==> (1997 9:00 AM EDT) September 2,9,16,23,30
2341 dtstart
2342 (ical:date-time-variant dtstart :day 9 :tz 'preserve)
2343 (ical:date-time-variant dtstart :day 16 :tz 'preserve)
2344 (ical:date-time-variant dtstart :day 23 :tz 'preserve)
2345 (ical:date-time-variant dtstart :day 30 :tz 'preserve)
2346 ;; (1997 9:00 AM EST) November 4,11,18,25
2347 (ical:date-time-variant dtstart :month 11 :day 4
2348 :zone ict:est :dst nil)
2349 (ical:date-time-variant dtstart :month 11 :day 11
2350 :zone ict:est :dst nil)
2351 (ical:date-time-variant dtstart :month 11 :day 11
2352 :zone ict:est :dst nil)
2353 (ical:date-time-variant dtstart :month 11 :day 25
2354 :zone ict:est :dst nil)
2355 ;; (1998 9:00 AM EST) January 6,13,20,27;March 3,10,17,24,31
2356 (ical:date-time-variant dtstart :year 1998 :month 1 :day 6
2357 :zone ict:est :dst nil)
2358 (ical:date-time-variant dtstart :year 1998 :month 1 :day 13
2359 :zone ict:est :dst nil)
2360 (ical:date-time-variant dtstart :year 1998 :month 1 :day 20
2361 :zone ict:est :dst nil)
2362 (ical:date-time-variant dtstart :year 1998 :month 1 :day 27
2363 :zone ict:est :dst nil)
2364 (ical:date-time-variant dtstart :year 1998 :month 3 :day 3
2365 :zone ict:est :dst nil)
2366 (ical:date-time-variant dtstart :year 1998 :month 3 :day 10
2367 :zone ict:est :dst nil)
2368 (ical:date-time-variant dtstart :year 1998 :month 3 :day 17
2369 :zone ict:est :dst nil)
2370 (ical:date-time-variant dtstart :year 1998 :month 3 :day 24
2371 :zone ict:est :dst nil)
2372 (ical:date-time-variant dtstart :year 1998 :month 3 :day 31
2373 :zone ict:est :dst nil))
2374 :nonmembers
2375 ;; e.g. Tuesdays in December 1997:
2376 (list
2377 (ical:date-time-variant dtstart :month 12 :day 2 :zone ict:est :dst nil)
2378 (ical:date-time-variant dtstart :month 12 :day 9 :zone ict:est :dst nil)
2379 (ical:date-time-variant dtstart :month 12 :day 16 :zone ict:est :dst nil)
2380 (ical:date-time-variant dtstart :month 12 :day 23 :zone ict:est :dst nil)
2381 (ical:date-time-variant dtstart :month 12 :day 30 :zone ict:est :dst nil))
2382 :source rfc5545-sec3.8.5.3/22)
2383
2384(ict:rrule-test
2385 "RRULE:FREQ=YEARLY;COUNT=10;BYMONTH=6,7\n"
2386 "Yearly in June and July for 10 occurrences"
2387 :tz ict:tz-eastern
2388 :dtstart (ical:make-date-time :year 1997 :month 6 :day 10
2389 :hour 9 :minute 0 :second 0
2390 :zone ict:edt :dst t)
2391 ;; Note: Since none of the BYDAY, BYMONTHDAY, or BYYEARDAY
2392 ;; clauses are specified, the month day is gotten from "DTSTART"
2393 :members
2394 ;; ==> (1997 9:00 AM EDT) June 10;July 10
2395 ;; (1998 9:00 AM EDT) June 10;July 10
2396 ;; (1999 9:00 AM EDT) June 10;July 10
2397 ;; (2000 9:00 AM EDT) June 10;July 10
2398 ;; (2001 9:00 AM EDT) June 10;July 10
2399 (mapcan
2400 (lambda (y)
2401 (list
2402 (ical:date-time-variant dtstart :year y :month 6 :tz 'preserve)
2403 (ical:date-time-variant dtstart :year y :month 7 :tz 'preserve)))
2404 (number-sequence 1997 2001))
2405 :source rfc5545-sec3.8.5.3/23)
2406
2407(ict:rrule-test
2408 "RRULE:FREQ=YEARLY;INTERVAL=2;COUNT=10;BYMONTH=1,2,3\n"
2409 "Every other year on January, February, and March for 10 occurrences"
2410 :tz ict:tz-eastern
2411 :dtstart (ical:make-date-time :year 1997 :month 3 :day 10
2412 :hour 9 :minute 0 :second 0
2413 :zone ict:est :dst nil)
2414 :members
2415 ;; ==> (1997 9:00 AM EST) March 10
2416 ;; (1999 9:00 AM EST) January 10;February 10;March 10
2417 ;; (2001 9:00 AM EST) January 10;February 10;March 10
2418 ;; (2003 9:00 AM EST) January 10;February 10;March 10
2419 (cons
2420 dtstart
2421 ;; FIXME: this mapcan appears to produce a spurious warning:
2422 (with-suppressed-warnings ((ignored-return-value mapcan))
2423 (mapcan
2424 (lambda (y)
2425 (list
2426 (ical:date-time-variant dtstart :year y :month 1 :tz 'preserve)
2427 (ical:date-time-variant dtstart :year y :month 2 :tz 'preserve)
2428 (ical:date-time-variant dtstart :year y :month 3 :tz 'preserve)))
2429 (list 1999 2001 2003))))
2430 :source rfc5545-sec3.8.5.3/24)
2431
2432(ict:rrule-test
2433"RRULE:FREQ=YEARLY;INTERVAL=3;COUNT=10;BYYEARDAY=1,100,200\n"
2434"Every third year on the 1st, 100th, and 200th day for 10 occurrences"
2435 :tz ict:tz-eastern
2436 :dtstart (ical:make-date-time :year 1997 :month 1 :day 1
2437 :hour 9 :minute 0 :second 0
2438 :zone ict:est :dst nil)
2439
2440 :members
2441 (list
2442 ;; ==> (1997 9:00 AM EST) January 1
2443 dtstart
2444 ;; (1997 9:00 AM EDT) April 10;July 19
2445 (ical:date-time-variant dtstart :month 4 :day 10 :zone ict:edt :dst t)
2446 (ical:date-time-variant dtstart :month 7 :day 19 :zone ict:edt :dst t)
2447 ;; (2000 9:00 AM EST) January 1
2448 (ical:date-time-variant dtstart :year 2000 :tz 'preserve)
2449 ;; (2000 9:00 AM EDT) April 9;July 18
2450 (ical:date-time-variant dtstart :year 2000 :month 4 :day 9 :zone ict:edt :dst t)
2451 (ical:date-time-variant dtstart :year 2000 :month 7 :day 18 :zone ict:edt :dst t)
2452 ;; (2003 9:00 AM EST) January 1
2453 (ical:date-time-variant dtstart :year 2003 :tz 'preserve)
2454 ;; (2003 9:00 AM EDT) April 10;July 19
2455 (ical:date-time-variant dtstart :year 2003 :month 4 :day 10 :zone ict:edt :dst t)
2456 (ical:date-time-variant dtstart :year 2003 :month 7 :day 19 :zone ict:edt :dst t)
2457 ;; (2006 9:00 AM EST) January 1
2458 (ical:date-time-variant dtstart :year 2006 :tz 'preserve))
2459 :source rfc5545-sec3.8.5.3/25)
2460
2461(ict:rrule-test
2462 "RRULE:FREQ=YEARLY;BYDAY=20MO\n"
2463 "Every 20th Monday of the year, forever"
2464 :tz ict:tz-eastern
2465 :dtstart (ical:make-date-time :year 1997 :month 5 :day 19
2466 :hour 9 :minute 0 :second 0
2467 :zone ict:edt :dst t)
2468 :members
2469 (list
2470 ;; ==> (1997 9:00 AM EDT) May 19
2471 ;; (1998 9:00 AM EDT) May 18
2472 ;; (1999 9:00 AM EDT) May 17
2473 ;; ...
2474 dtstart
2475 (ical:date-time-variant dtstart :year 1998 :day 18 :tz 'preserve)
2476 (ical:date-time-variant dtstart :year 1999 :day 17 :tz 'preserve))
2477 :source rfc5545-sec3.8.5.3/26)
2478
2479(ict:rrule-test
2480 "RRULE:FREQ=YEARLY;BYWEEKNO=20;BYDAY=MO\n"
2481 "Every year on Monday in Week 20 (where the week starts Monday), forever"
2482 :tz ict:tz-eastern
2483 :dtstart
2484 (ical:make-date-time :year 1997 :month 5 :day 12
2485 :hour 9 :minute 0 :second 0
2486 :zone ict:edt :dst t)
2487 :members
2488 (list
2489 (ical:date-time-variant dtstart :year 1998 :day 11 :tz 'preserve)
2490 (ical:date-time-variant dtstart :year 1999 :day 17 :tz 'preserve))
2491 :nonmembers
2492 (list
2493 (ical:date-time-variant dtstart :year 1998 :day 12 :tz 'preserve) ; a Tuesday
2494 (ical:date-time-variant dtstart :year 1998 :day 18 :tz 'preserve)) ; wrong weekno
2495 :source rfc5545-sec3.8.5.3/27)
2496
2497(ict:rrule-test
2498 "RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=TH\n"
2499 "Every Thursday in March, forever"
2500 :tz ict:tz-eastern
2501 :dtstart (ical:make-date-time :year 1997 :month 3 :day 13
2502 :hour 9 :minute 0 :second 0
2503 :zone ict:est :dst nil)
2504 :members
2505 (append
2506 ;; ==> (1997 9:00 AM EST) March 13,20,27
2507 (mapcar
2508 (lambda (d)
2509 (ical:date-time-variant dtstart :day d :tz 'preserve))
2510 (list 13 20 27))
2511 ;; (1998 9:00 AM EST) March 5,12,19,26
2512 (mapcar
2513 (lambda (d)
2514 (ical:date-time-variant dtstart :year 1998 :day d :tz 'preserve))
2515 (list 5 12 19 26))
2516 ;; (1999 9:00 AM EST) March 4,11,18,25
2517 (mapcar
2518 (lambda (d)
2519 (ical:date-time-variant dtstart :year 1999 :day d :tz 'preserve))
2520 (list 4 11 18 25)))
2521 :source rfc5545-sec3.8.5.3/28)
2522
2523(ict:rrule-test
2524"RRULE:FREQ=YEARLY;BYDAY=TH;BYMONTH=6,7,8\n"
2525"Every Thursday, but only during June, July, and August, forever"
2526 :tz ict:tz-eastern
2527 :dtstart (ical:make-date-time :year 1997 :month 6 :day 5
2528 :hour 9 :minute 0 :second 0
2529 :zone ict:edt :dst t)
2530 :members
2531 (append
2532 ;; ==> (1997 9:00 AM EDT) June 5,12,19,26;July 3,10,17,24,31;
2533 ;; August 7,14,21,28
2534 (mapcar
2535 (lambda (d)
2536 (ical:date-time-variant dtstart :day d :tz 'preserve))
2537 (list 5 12 19 26))
2538 (mapcar
2539 (lambda (d)
2540 (ical:date-time-variant dtstart :month 7 :day d :tz 'preserve))
2541 (list 3 10 17 24 31))
2542 (mapcar
2543 (lambda (d)
2544 (ical:date-time-variant dtstart :month 8 :day d :tz 'preserve))
2545 (list 7 14 21 28))
2546 ;; (1998 9:00 AM EDT) June 4,11,18,25;July 2,9,16,23,30;
2547 ;; August 6,13,20,27
2548 (mapcar
2549 (lambda (d)
2550 (ical:date-time-variant dtstart :year 1998 :day d :tz 'preserve))
2551 (list 4 11 18 25))
2552 (mapcar
2553 (lambda (d)
2554 (ical:date-time-variant dtstart :year 1998 :month 7 :day d :tz 'preserve))
2555 (list 2 9 16 23 30))
2556 (mapcar
2557 (lambda (d)
2558 (ical:date-time-variant dtstart :year 1998 :month 8 :day d :tz 'preserve))
2559 (list 6 13 20 27))
2560 ;; (1999 9:00 AM EDT) June 3,10,17,24;July 1,8,15,22,29;
2561 ;; August 5,12,19,26
2562 (mapcar
2563 (lambda (d)
2564 (ical:date-time-variant dtstart :year 1999 :day d :tz 'preserve))
2565 (list 3 10 17 24))
2566 (mapcar
2567 (lambda (d)
2568 (ical:date-time-variant dtstart :year 1999 :month 7 :day d :tz 'preserve))
2569 (list 1 8 15 22 29))
2570 (mapcar
2571 (lambda (d)
2572 (ical:date-time-variant dtstart :year 1999 :month 8 :day d :tz 'preserve))
2573 (list 5 12 19 26)))
2574 :source rfc5545-sec3.8.5.3/29)
2575
2576(ict:rrule-test
2577 "RRULE:FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13\n"
2578 "Every Friday the 13th, forever, *excluding* DTSTART "
2579 :tz ict:tz-eastern
2580 :dtstart (ical:make-date-time :year 1997 :month 9 :day 2
2581 :hour 9 :minute 0 :second 0
2582 :zone ict:edt :dst t)
2583 :high (ical:make-date-time :year 2000 :month 10 :day 14
2584 :hour 0 :minute 0 :second 0
2585 :zone ict:edt :dst t)
2586 :exdates (list dtstart)
2587 :members
2588 (list
2589 ;; ==> (1998 9:00 AM EST) February 13;March 13;November 13
2590 ;; (1999 9:00 AM EDT) August 13
2591 ;; (2000 9:00 AM EDT) October 13
2592 ;; ...
2593 (ical:date-time-variant dtstart :year 1998 :month 2 :day 13
2594 :zone ict:est :dst nil)
2595 (ical:date-time-variant dtstart :year 1998 :month 3 :day 13
2596 :zone ict:est :dst nil)
2597 (ical:date-time-variant dtstart :year 1998 :month 11 :day 13
2598 :zone ict:est :dst nil)
2599 (ical:date-time-variant dtstart :year 1999 :month 8 :day 13 :tz 'preserve)
2600 (ical:date-time-variant dtstart :year 2000 :month 10 :day 13 :tz 'preserve))
2601 :source rfc5545-sec3.8.5.3/30)
2602
2603(ict:rrule-test
2604 "RRULE:FREQ=MONTHLY;BYDAY=SA;BYMONTHDAY=7,8,9,10,11,12,13\n"
2605 "The first Saturday that follows the first Sunday of the month, forever"
2606 :tz ict:tz-eastern
2607 :dtstart (ical:make-date-time :year 1997 :month 9 :day 13
2608 :hour 9 :minute 0 :second 0
2609 :zone ict:edt :dst t)
2610 :high (ical:make-date-time :year 1998 :month 6 :day 14
2611 :hour 0 :minute 0 :second 0
2612 :zone ict:edt :dst t)
2613 :members
2614 (list
2615 ;; ==> (1997 9:00 AM EDT) September 13;October 11
2616 dtstart
2617 (ical:date-time-variant dtstart :month 10 :day 11 :tz 'preserve)
2618 ;; (1997 9:00 AM EST) November 8;December 13
2619 (ical:date-time-variant dtstart :month 11 :day 8 :zone ict:est :dst nil)
2620 (ical:date-time-variant dtstart :month 12 :day 13 :zone ict:est :dst nil)
2621 ;; (1998 9:00 AM EST) January 10;February 7;March 7
2622 (ical:date-time-variant dtstart :year 1998 :month 1 :day 10
2623 :zone ict:est :dst nil)
2624 (ical:date-time-variant dtstart :year 1998 :month 2 :day 7
2625 :zone ict:est :dst nil)
2626 (ical:date-time-variant dtstart :year 1998 :month 3 :day 7
2627 :zone ict:est :dst nil)
2628 ;; (1998 9:00 AM EDT) April 11;May 9;June 13...
2629 (ical:date-time-variant dtstart :year 1998 :month 4 :day 11 :tz 'preserve)
2630 (ical:date-time-variant dtstart :year 1998 :month 5 :day 9 :tz 'preserve)
2631 (ical:date-time-variant dtstart :year 1998 :month 6 :day 13 :tz 'preserve))
2632 :source rfc5545-sec3.8.5.3/31)
2633
2634(ict:rrule-test
2635 "RRULE:FREQ=YEARLY;INTERVAL=4;BYMONTH=11;BYDAY=TU;BYMONTHDAY=2,3,4,5,6,7,8\n"
2636 "Every 4 years, the first Tuesday after a Monday in November, forever
2637(U.S. Presidential Election day)"
2638 :tz ict:tz-eastern
2639 :dtstart (ical:make-date-time :year 1996 :month 11 :day 5
2640 :hour 9 :minute 0 :second 0
2641 :zone ict:est :dst nil)
2642
2643 :members
2644 (list
2645 ;; ==> (1996 9:00 AM EST) November 5
2646 dtstart
2647 ;; (2000 9:00 AM EST) November 7
2648 (ical:date-time-variant dtstart :year 2000 :day 7 :tz 'preserve)
2649 ;; (2004 9:00 AM EST) November 2
2650 (ical:date-time-variant dtstart :year 2004 :day 2 :tz 'preserve))
2651 :source rfc5545-sec3.8.5.3/32)
2652
2653(ict:rrule-test
2654 "RRULE:FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=3\n"
2655 "The third instance into the month of one of Tuesday, Wednesday, or
2656Thursday, for the next 3 months"
2657 ;; TODO: Yikes, why is this so slow??
2658 :tz ict:tz-eastern
2659 :dtstart (ical:make-date-time :year 1997 :month 9 :day 4
2660 :hour 9 :minute 0 :second 0
2661 :zone ict:edt :dst t)
2662
2663 :members
2664 (list
2665 ;; ==> (1997 9:00 AM EDT) September 4;October 7
2666 ;; (1997 9:00 AM EST) November 6
2667 dtstart
2668 (ical:date-time-variant dtstart :month 10 :day 7 :tz 'preserve)
2669 (ical:date-time-variant dtstart :month 11 :day 6 :zone ict:est :dst nil))
2670:source rfc5545-sec3.8.5.3/33)
2671
2672(ict:rrule-test
2673 "RRULE:FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-2\n"
2674 "The second-to-last weekday of the month"
2675 :tz ict:tz-eastern
2676 :dtstart (ical:make-date-time :year 1997 :month 9 :day 29
2677 :hour 9 :minute 0 :second 0
2678 :zone ict:edt :dst t)
2679 :high (ical:make-date-time :year 1998 :month 4 :day 1
2680 :hour 0 :minute 0 :second 0
2681 :zone ict:est :dst nil)
2682 :members
2683 (list
2684 ;; ==> (1997 9:00 AM EDT) September 29
2685 dtstart
2686 ;; (1997 9:00 AM EST) October 30;November 27;December 30
2687 (ical:date-time-variant dtstart :month 10 :day 30 :zone ict:est :dst nil)
2688 (ical:date-time-variant dtstart :month 11 :day 27 :zone ict:est :dst nil)
2689 (ical:date-time-variant dtstart :month 12 :day 30 :zone ict:est :dst nil)
2690 ;; (1998 9:00 AM EST) January 29;February 26;March 30
2691 (ical:date-time-variant dtstart :year 1998 :month 1 :day 29
2692 :zone ict:est :dst nil)
2693 (ical:date-time-variant dtstart :year 1998 :month 2 :day 26
2694 :zone ict:est :dst nil)
2695 (ical:date-time-variant dtstart :year 1998 :month 3 :day 30
2696 :zone ict:est :dst nil))
2697 :source rfc5545-sec3.8.5.3/34)
2698
2699(ict:rrule-test
2700 ;; corrected, see Errata ID 3883: https://www.rfc-editor.org/errata/eid3883
2701 "RRULE:FREQ=HOURLY;INTERVAL=3;UNTIL=19970902T210000Z\n"
2702 "Every 3 hours from 9:00 AM to 5:00 PM on a specific day"
2703 :tz ict:tz-eastern
2704 :dtstart (ical:make-date-time :year 1997 :month 9 :day 2
2705 :hour 9 :minute 0 :second 0
2706 :zone ict:edt :dst t)
2707
2708 :members
2709 (list
2710 ;; ==> (September 2, 1997 EDT) 09:00,12:00,15:00
2711 dtstart
2712 (ical:date-time-variant dtstart :hour 12 :tz 'preserve)
2713 (ical:date-time-variant dtstart :hour 15 :tz 'preserve))
2714 :source rfc5545-sec3.8.5.3/35)
2715
2716(ict:rrule-test
2717 "RRULE:FREQ=MINUTELY;INTERVAL=15;COUNT=6\n"
2718 "Every 15 minutes for 6 occurrences"
2719 :tz ict:tz-eastern
2720 :dtstart (ical:make-date-time :year 1997 :month 9 :day 2
2721 :hour 9 :minute 0 :second 0
2722 :zone ict:edt :dst t)
2723
2724 :members
2725 (list
2726 ;; ==> (September 2, 1997 EDT) 09:00,09:15,09:30,09:45,10:00,10:15
2727 dtstart
2728 (ical:date-time-variant dtstart :minute 15 :tz 'preserve)
2729 (ical:date-time-variant dtstart :minute 30 :tz 'preserve)
2730 (ical:date-time-variant dtstart :minute 45 :tz 'preserve)
2731 (ical:date-time-variant dtstart :hour 10 :minute 0 :tz 'preserve)
2732 (ical:date-time-variant dtstart :hour 10 :minute 15 :tz 'preserve))
2733 :source rfc5545-sec3.8.5.3/36)
2734
2735(ict:rrule-test
2736 "RRULE:FREQ=MINUTELY;INTERVAL=90;COUNT=4\n"
2737 "Every hour and a half for 4 occurrences"
2738 :tz ict:tz-eastern
2739 :dtstart (ical:make-date-time :year 1997 :month 9 :day 2
2740 :hour 9 :minute 0 :second 0
2741 :zone ict:edt :dst t)
2742 :members
2743 (list
2744 ;; ==> (September 2, 1997 EDT) 09:00,10:30;12:00;13:30
2745 dtstart
2746 (ical:date-time-variant dtstart :hour 10 :minute 30 :tz 'preserve)
2747 (ical:date-time-variant dtstart :hour 12 :minute 0 :tz 'preserve)
2748 (ical:date-time-variant dtstart :hour 13 :minute 30 :tz 'preserve))
2749 :source rfc5545-sec3.8.5.3/37)
2750
2751(ict:rrule-test
2752 "RRULE:FREQ=DAILY;BYHOUR=9,10,11,12,13,14,15,16;BYMINUTE=0,20,40\n"
2753 "Every 20 minutes from 9:00 AM to 4:40 PM every day"
2754 :tz ict:tz-eastern
2755 :dtstart (ical:make-date-time :year 1997 :month 9 :day 2
2756 :hour 9 :minute 0 :second 0
2757 :zone ict:edt :dst t)
2758 :high (ical:make-date-time :year 1997 :month 9 :day 4
2759 :hour 0 :minute 0 :second 0
2760 :zone ict:edt :dst t)
2761 :members
2762 (append
2763 ;; ==> (September 2, 1997 EDT) 9:00,9:20,9:40,10:00,10:20,
2764 ;; ... 16:00,16:20,16:40
2765 (mapcan
2766 (lambda (h)
2767 (list
2768 (ical:date-time-variant dtstart :hour h :minute 0 :tz 'preserve)
2769 (ical:date-time-variant dtstart :hour h :minute 20 :tz 'preserve)
2770 (ical:date-time-variant dtstart :hour h :minute 40 :tz 'preserve)))
2771 (number-sequence 9 16))
2772 ;; (September 3, 1997 EDT) 9:00,9:20,9:40,10:00,10:20,
2773 ;; ...16:00,16:20,16:40
2774 (mapcan
2775 (lambda (h)
2776 (list
2777 (ical:date-time-variant dtstart :hour h :day 3 :minute 0 :tz 'preserve)
2778 (ical:date-time-variant dtstart :hour h :day 3 :minute 20 :tz 'preserve)
2779 (ical:date-time-variant dtstart :hour h :day 3 :minute 40 :tz 'preserve)))
2780 (number-sequence 9 16)))
2781 :source rfc5545-sec3.8.5.3/38)
2782
2783(ict:rrule-test
2784 "RRULE:FREQ=MINUTELY;INTERVAL=20;BYHOUR=9,10,11,12,13,14,15,16\n"
2785 "Every 20 minutes from 9:00 AM to 4:40 PM every day
2786(Alternative rule for the previous example)"
2787 :tags '(:expensive-test)
2788 :tz ict:tz-eastern
2789 :dtstart (ical:make-date-time :year 1997 :month 9 :day 2
2790 :hour 9 :minute 0 :second 0
2791 :zone ict:edt :dst t)
2792 :high (ical:make-date-time :year 1997 :month 9 :day 4
2793 :hour 0 :minute 0 :second 0
2794 :zone ict:edt :dst t)
2795 :members
2796 (append
2797 ;; ==> (September 2, 1997 EDT) 9:00,9:20,9:40,10:00,10:20,
2798 ;; ... 16:00,16:20,16:40
2799 (mapcan
2800 (lambda (h)
2801 (list
2802 (ical:date-time-variant dtstart :hour h :minute 0 :tz 'preserve)
2803 (ical:date-time-variant dtstart :hour h :minute 20 :tz 'preserve)
2804 (ical:date-time-variant dtstart :hour h :minute 40 :tz 'preserve)))
2805 (number-sequence 9 16))
2806 ;; (September 3, 1997 EDT) 9:00,9:20,9:40,10:00,10:20,
2807 ;; ...16:00,16:20,16:40
2808 (mapcan
2809 (lambda (h)
2810 (list
2811 (ical:date-time-variant dtstart :hour h :day 3 :minute 0 :tz 'preserve)
2812 (ical:date-time-variant dtstart :hour h :day 3 :minute 20 :tz 'preserve)
2813 (ical:date-time-variant dtstart :hour h :day 3 :minute 40 :tz 'preserve)))
2814 (number-sequence 9 16)))
2815:source rfc5545-sec3.8.5.3/39)
2816
2817(ict:rrule-test
2818 "RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=MO\n"
2819 "An example where the days generated makes a difference because of WKST:
2820every other week on Tuesday and Sunday, week start Monday, for four recurrences"
2821 :tz ict:tz-eastern
2822 :dtstart (ical:make-date-time :year 1997 :month 8 :day 5
2823 :hour 9 :minute 0 :second 0
2824 :zone ict:edt :dst t)
2825 :members
2826 (list
2827 ;; ==> (1997 EDT) August 5,10,19,24
2828 dtstart
2829 (ical:date-time-variant dtstart :day 10 :tz 'preserve)
2830 (ical:date-time-variant dtstart :day 19 :tz 'preserve)
2831 (ical:date-time-variant dtstart :day 24 :tz 'preserve))
2832 :source rfc5545-sec3.8.5.3/40)
2833
2834(ict:rrule-test
2835 "RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=SU\n"
2836 "An example where the days generated makes a difference because of WKST:
2837every other week on Tuesday and Sunday, week start Sunday, for four recurrences"
2838 :tz ict:tz-eastern
2839 :dtstart (ical:make-date-time :year 1997 :month 8 :day 5
2840 :hour 9 :minute 0 :second 0
2841 :zone ict:edt :dst t)
2842 :members
2843 (list
2844 ;; ==> (1997 EDT) August 5,17,19,31
2845 dtstart
2846 (ical:date-time-variant dtstart :day 17 :tz 'preserve)
2847 (ical:date-time-variant dtstart :day 19 :tz 'preserve)
2848 (ical:date-time-variant dtstart :day 31 :tz 'preserve))
2849 :source rfc5545-sec3.8.5.3/41)
2850
2851(ict:rrule-test
2852 "RRULE:FREQ=MONTHLY;BYMONTHDAY=15,30;COUNT=5\n"
2853 "An example where an invalid date (i.e., February 30) is ignored."
2854 :tz ict:tz-eastern
2855 :dtstart (ical:make-date-time :year 2007 :month 1 :day 15
2856 :hour 9 :minute 0 :second 0
2857 :zone ict:est :dst nil)
2858 :high (ical:make-date-time :year 2007 :month 4 :day 1
2859 :hour 0 :minute 0 :second 0
2860 :zone ict:edt :dst t)
2861 :members
2862 (list
2863 ;; ==> (2007 EST) January 15,30
2864 ;; (2007 EST) February 15
2865 ;; (2007 EDT) March 15,30
2866 dtstart
2867 (ical:date-time-variant dtstart :day 30 :tz 'preserve)
2868 (ical:date-time-variant dtstart :month 2 :day 15 :tz 'preserve)
2869 (ical:date-time-variant dtstart :month 3 :day 15 :zone ict:edt :dst t)
2870 (ical:date-time-variant dtstart :month 3 :day 30 :zone ict:edt :dst t))
2871 :nonmembers
2872 (list
2873 (ical:date-time-variant dtstart :month 2 :day 28 :tz 'preserve)
2874 (ical:date-time-variant dtstart :month 2 :day 30 :tz 'preserve))
2875 :source rfc5545-sec3.8.5.3/42)
2876
2877;; Local Variables:
2878;; read-symbol-shorthands: (("ict:" . "icalendar-test-") ("icr:" . "icalendar-recur-") ("ical:" . "icalendar-"))
2879;; End:
2880;;; icalendar-recur-tests.el ends here
diff --git a/test/lisp/calendar/icalendar-resources/import-legacy-function.ics b/test/lisp/calendar/icalendar-resources/import-legacy-function.ics
new file mode 100644
index 00000000000..760131b8192
--- /dev/null
+++ b/test/lisp/calendar/icalendar-resources/import-legacy-function.ics
@@ -0,0 +1,16 @@
1BEGIN:VCALENDAR
2PRODID:-//Emacs//NONSGML icalendar.el//EN
3VERSION:2.0
4BEGIN:VEVENT
5SUMMARY:Testing legacy `icalendar-import-format' function
6DESCRIPTION:described
7CLASS:private
8LOCATION:somewhere
9ORGANIZER;CN="Baz Foo":mailto:baz@example.com
10STATUS:CONFIRMED
11URL:http://example.com/foo/baz
12UID:some-unique-id-here
13DTSTART;VALUE=DATE-TIME:20250919T090000
14DTEND;VALUE=DATE-TIME:20250919T113000
15END:VEVENT
16END:VCALENDAR
diff --git a/test/lisp/calendar/icalendar-resources/import-legacy-vars.ics b/test/lisp/calendar/icalendar-resources/import-legacy-vars.ics
new file mode 100644
index 00000000000..8aa80277b09
--- /dev/null
+++ b/test/lisp/calendar/icalendar-resources/import-legacy-vars.ics
@@ -0,0 +1,16 @@
1BEGIN:VCALENDAR
2PRODID:-//Emacs//NONSGML icalendar.el//EN
3VERSION:2.0
4BEGIN:VEVENT
5SUMMARY:Testing legacy `icalendar-import-format*' vars
6DESCRIPTION:described
7CLASS:private
8LOCATION:somewhere
9ORGANIZER;CN="Baz Foo":mailto:baz@example.com
10STATUS:CONFIRMED
11URL:http://example.com/foo/baz
12UID:some-unique-id-here
13DTSTART;VALUE=DATE-TIME:20250919T090000
14DTEND;VALUE=DATE-TIME:20250919T113000
15END:VEVENT
16END:VCALENDAR
diff --git a/test/lisp/calendar/icalendar-resources/import-non-recurring-all-day.ics b/test/lisp/calendar/icalendar-resources/import-non-recurring-all-day.ics
index 4efa8ffa133..53380b9734d 100644
--- a/test/lisp/calendar/icalendar-resources/import-non-recurring-all-day.ics
+++ b/test/lisp/calendar/icalendar-resources/import-non-recurring-all-day.ics
@@ -1,9 +1,8 @@
1BEGIN:VCALENDAR 1BEGIN:VCALENDAR
2PRODID:-//Emacs//NONSGML icalendar.el//EN 2PRODID:-//Emacs//NONSGML icalendar.el//EN
3VERSION:2.0 3VERSION:2.0
4BEGIN:VEVENT 4BEGIN:VEVENT
5SUMMARY:non-recurring allday 5SUMMARY:non-recurring allday
6DTSTART;VALUE=DATE-TIME:20030919 6DTSTART;VALUE=DATE:20030919
7END:VEVENT 7END:VEVENT
8END:VCALENDAR 8END:VCALENDAR
9
diff --git a/test/lisp/calendar/icalendar-resources/import-rrule-anniversary.ics b/test/lisp/calendar/icalendar-resources/import-rrule-anniversary.ics
index 2996f494167..de402a29d26 100644
--- a/test/lisp/calendar/icalendar-resources/import-rrule-anniversary.ics
+++ b/test/lisp/calendar/icalendar-resources/import-rrule-anniversary.ics
@@ -1,11 +1,10 @@
1BEGIN:VCALENDAR 1BEGIN:VCALENDAR
2PRODID:-//Emacs//NONSGML icalendar.el//EN 2PRODID:-//Emacs//NONSGML icalendar.el//EN
3VERSION:2.0 3VERSION:2.0
4BEGIN:VEVENT 4BEGIN:VEVENT
5DTSTART;VALUE=DATE:20040815 5DTSTART;VALUE=DATE:20040815
6DTEND;VALUE=DATE:20040816 6SUMMARY:Maria Himmelfahrt
7SUMMARY:Maria Himmelfahrt 7RRULE:FREQ=YEARLY
8RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=8 8END:VEVENT
9END:VEVENT 9END:VCALENDAR
10END:VCALENDAR 10
11
diff --git a/test/lisp/calendar/icalendar-resources/import-rrule-daily-with-exceptions.ics b/test/lisp/calendar/icalendar-resources/import-rrule-daily-with-exceptions.ics
index 5284bf42d8b..350e1aa0f24 100644
--- a/test/lisp/calendar/icalendar-resources/import-rrule-daily-with-exceptions.ics
+++ b/test/lisp/calendar/icalendar-resources/import-rrule-daily-with-exceptions.ics
@@ -1,12 +1,11 @@
1BEGIN:VCALENDAR 1BEGIN:VCALENDAR
2PRODID:-//Emacs//NONSGML icalendar.el//EN 2PRODID:-//Emacs//NONSGML icalendar.el//EN
3VERSION:2.0 3VERSION:2.0
4BEGIN:VEVENT 4BEGIN:VEVENT
5SUMMARY:rrule daily with exceptions 5SUMMARY:rrule daily with exceptions
6DTSTART;VALUE=DATE-TIME:20030919T090000 6DTSTART;VALUE=DATE-TIME:20030919T090000
7DTEND;VALUE=DATE-TIME:20030919T113000 7DTEND;VALUE=DATE-TIME:20030919T113000
8RRULE:FREQ=DAILY;INTERVAL=2 8RRULE:FREQ=DAILY;INTERVAL=2
9EXDATE:20030921,20030925 9EXDATE;VALUE=DATE:20030921,20030925
10END:VEVENT 10END:VEVENT
11END:VCALENDAR 11END:VCALENDAR
12
diff --git a/test/lisp/calendar/icalendar-resources/import-rrule-daily.ics b/test/lisp/calendar/icalendar-resources/import-rrule-daily.ics
index 6d013b0b4f6..93ed08065bc 100644
--- a/test/lisp/calendar/icalendar-resources/import-rrule-daily.ics
+++ b/test/lisp/calendar/icalendar-resources/import-rrule-daily.ics
@@ -1,11 +1,11 @@
1BEGIN:VCALENDAR 1BEGIN:VCALENDAR
2PRODID:-//Emacs//NONSGML icalendar.el//EN 2PRODID:-//Emacs//NONSGML icalendar.el//EN
3VERSION:2.0 3VERSION:2.0
4BEGIN:VEVENT 4BEGIN:VEVENT
5SUMMARY:rrule daily 5SUMMARY:rrule daily
6DTSTART;VALUE=DATE-TIME:20030919T090000 6DTSTART;VALUE=DATE-TIME:20030919T090000
7DTEND;VALUE=DATE-TIME:20030919T113000 7DTEND;VALUE=DATE-TIME:20030919T113000
8RRULE:FREQ=DAILY; 8RRULE:FREQ=DAILY
9END:VEVENT 9END:VEVENT
10END:VCALENDAR 10END:VCALENDAR
11 11
diff --git a/test/lisp/calendar/icalendar-resources/import-rrule-monthly-no-end.ics b/test/lisp/calendar/icalendar-resources/import-rrule-monthly-no-end.ics
index b871658600a..9448ca058f8 100644
--- a/test/lisp/calendar/icalendar-resources/import-rrule-monthly-no-end.ics
+++ b/test/lisp/calendar/icalendar-resources/import-rrule-monthly-no-end.ics
@@ -1,11 +1,11 @@
1BEGIN:VCALENDAR 1BEGIN:VCALENDAR
2PRODID:-//Emacs//NONSGML icalendar.el//EN 2PRODID:-//Emacs//NONSGML icalendar.el//EN
3VERSION:2.0 3VERSION:2.0
4BEGIN:VEVENT 4BEGIN:VEVENT
5SUMMARY:rrule monthly no end 5SUMMARY:rrule monthly no end
6DTSTART;VALUE=DATE-TIME:20030919T090000 6DTSTART;VALUE=DATE-TIME:20030919T090000
7DTEND;VALUE=DATE-TIME:20030919T113000 7DTEND;VALUE=DATE-TIME:20030919T113000
8RRULE:FREQ=MONTHLY; 8RRULE:FREQ=MONTHLY
9END:VEVENT 9END:VEVENT
10END:VCALENDAR 10END:VCALENDAR
11 11
diff --git a/test/lisp/calendar/icalendar-resources/import-rrule-monthly-with-end.ics b/test/lisp/calendar/icalendar-resources/import-rrule-monthly-with-end.ics
index d8a1fe2e5af..0434765d613 100644
--- a/test/lisp/calendar/icalendar-resources/import-rrule-monthly-with-end.ics
+++ b/test/lisp/calendar/icalendar-resources/import-rrule-monthly-with-end.ics
@@ -1,11 +1,10 @@
1BEGIN:VCALENDAR 1BEGIN:VCALENDAR
2PRODID:-//Emacs//NONSGML icalendar.el//EN 2PRODID:-//Emacs//NONSGML icalendar.el//EN
3VERSION:2.0 3VERSION:2.0
4BEGIN:VEVENT 4BEGIN:VEVENT
5SUMMARY:rrule monthly with end 5SUMMARY:rrule monthly with end
6DTSTART;VALUE=DATE-TIME:20030919T090000 6DTSTART;VALUE=DATE-TIME:20030919T090000
7DTEND;VALUE=DATE-TIME:20030919T113000 7DTEND;VALUE=DATE-TIME:20030919T113000
8RRULE:FREQ=MONTHLY;UNTIL=20050819; 8RRULE:FREQ=MONTHLY;UNTIL=20050819
9END:VEVENT 9END:VEVENT
10END:VCALENDAR 10END:VCALENDAR
11
diff --git a/test/lisp/calendar/icalendar-resources/import-rrule-weekly.ics b/test/lisp/calendar/icalendar-resources/import-rrule-weekly.ics
index c3f0b8ae933..44b6f44e2e0 100644
--- a/test/lisp/calendar/icalendar-resources/import-rrule-weekly.ics
+++ b/test/lisp/calendar/icalendar-resources/import-rrule-weekly.ics
@@ -1,11 +1,11 @@
1BEGIN:VCALENDAR 1BEGIN:VCALENDAR
2PRODID:-//Emacs//NONSGML icalendar.el//EN 2PRODID:-//Emacs//NONSGML icalendar.el//EN
3VERSION:2.0 3VERSION:2.0
4BEGIN:VEVENT 4BEGIN:VEVENT
5SUMMARY:rrule weekly 5SUMMARY:rrule weekly
6DTSTART;VALUE=DATE-TIME:20030919T090000 6DTSTART;VALUE=DATE-TIME:20030919T090000
7DTEND;VALUE=DATE-TIME:20030919T113000 7DTEND;VALUE=DATE-TIME:20030919T113000
8RRULE:FREQ=WEEKLY; 8RRULE:FREQ=WEEKLY
9END:VEVENT 9END:VEVENT
10END:VCALENDAR 10END:VCALENDAR
11 11
diff --git a/test/lisp/calendar/icalendar-resources/import-time-format-12hr-blank.ics b/test/lisp/calendar/icalendar-resources/import-time-format-12hr-blank.ics
new file mode 100644
index 00000000000..7f436df5391
--- /dev/null
+++ b/test/lisp/calendar/icalendar-resources/import-time-format-12hr-blank.ics
@@ -0,0 +1,9 @@
1BEGIN:VCALENDAR
2PRODID:-//Emacs//NONSGML icalendar.el//EN
3VERSION:2.0
4BEGIN:VEVENT
5SUMMARY:12hr blank-padded
6DTSTART;VALUE=DATE-TIME:20030919T090000
7DTEND;VALUE=DATE-TIME:20030919T113000
8END:VEVENT
9END:VCALENDAR
diff --git a/test/lisp/calendar/icalendar-resources/import-with-attachment.ics b/test/lisp/calendar/icalendar-resources/import-with-attachment.ics
new file mode 100644
index 00000000000..0b1201714ef
--- /dev/null
+++ b/test/lisp/calendar/icalendar-resources/import-with-attachment.ics
@@ -0,0 +1,11 @@
1BEGIN:VCALENDAR
2PRODID:-//Emacs//NONSGML icalendar.el//EN
3VERSION:2.0
4BEGIN:VEVENT
5UID:f9fee9a0-1231-4984-9078-f1357db352db
6SUMMARY:Has an attachment
7ATTACH;VALUE=BINARY;FMTTYPE=text/plain;ENCODING=BASE64:R3JlZXRpbmdzISBJIGFt
8 IGEgYmFzZTY0LWVuY29kZWQgZmlsZQ==
9DTSTART;VALUE=DATE-TIME:20030919T090000
10END:VEVENT
11END:VCALENDAR
diff --git a/test/lisp/calendar/icalendar-resources/import-with-timezone.ics b/test/lisp/calendar/icalendar-resources/import-with-timezone.ics
index 110a9835e41..0db619e4f0a 100644
--- a/test/lisp/calendar/icalendar-resources/import-with-timezone.ics
+++ b/test/lisp/calendar/icalendar-resources/import-with-timezone.ics
@@ -1,27 +1,27 @@
1BEGIN:VCALENDAR 1BEGIN:VCALENDAR
2BEGIN:VTIMEZONE 2BEGIN:VTIMEZONE
3TZID:fictional, nonexistent, arbitrary 3TZID:fictional
4BEGIN:STANDARD 4BEGIN:STANDARD
5DTSTART:20100101T000000 5DTSTART:20100101T000000
6TZOFFSETFROM:+0200 6TZOFFSETFROM:+0200
7TZOFFSETTO:-0200 7TZOFFSETTO:-0200
8RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=01 8RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=01
9END:STANDARD 9END:STANDARD
10BEGIN:DAYLIGHT 10BEGIN:DAYLIGHT
11DTSTART:20101201T000000 11DTSTART:20101201T000000
12TZOFFSETFROM:-0200 12TZOFFSETFROM:-0200
13TZOFFSETTO:+0200 13TZOFFSETTO:+0200
14RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=11 14RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=11
15END:DAYLIGHT 15END:DAYLIGHT
16END:VTIMEZONE 16END:VTIMEZONE
17BEGIN:VEVENT 17BEGIN:VEVENT
18SUMMARY:standardtime 18SUMMARY:standardtime
19DTSTART;TZID="fictional, nonexistent, arbitrary":20120115T120000 19DTSTART;TZID=fictional:20120115T120000
20DTEND;TZID="fictional, nonexistent, arbitrary":20120115T123000 20DTEND;TZID=fictional:20120115T123000
21END:VEVENT 21END:VEVENT
22BEGIN:VEVENT 22BEGIN:VEVENT
23SUMMARY:daylightsavingtime 23SUMMARY:daylightsavingtime
24DTSTART;TZID="fictional, nonexistent, arbitrary":20121215T120000 24DTSTART;TZID=fictional:20121215T120000
25DTEND;TZID="fictional, nonexistent, arbitrary":20121215T123000 25DTEND;TZID=fictional:20121215T123000
26END:VEVENT 26END:VEVENT
27END:VCALENDAR 27END:VCALENDAR
diff --git a/test/lisp/calendar/icalendar-tests.el b/test/lisp/calendar/icalendar-tests.el
index fa1d1169ed6..bd2097b2fb8 100644
--- a/test/lisp/calendar/icalendar-tests.el
+++ b/test/lisp/calendar/icalendar-tests.el
@@ -30,11 +30,34 @@
30;; Note: Watch the trailing blank that is added on import. 30;; Note: Watch the trailing blank that is added on import.
31 31
32;;; Code: 32;;; Code:
33
34(require 'ert) 33(require 'ert)
35(require 'ert-x) 34(require 'ert-x)
36(require 'icalendar) 35(require 'icalendar)
37 36
37;; Some variables in the icalendar-* namespace have now been aliased to
38;; diary-icalendar-*:
39(require 'diary-icalendar)
40
41(defmacro ical:deftest-obsolete (name _ &rest args-and-body)
42 "Define NAME as an obsolete icalendar.el test."
43 (let (args body)
44 (while (not body)
45 (cond ((stringp (car args-and-body))
46 (push (pop args-and-body) args)) ; docstring
47 ((keywordp (car args-and-body))
48 (push (pop args-and-body) args) ; keyword
49 (push (pop args-and-body) args)) ; value
50 (t
51 (setq body args-and-body)
52 (setq args (nreverse args)))))
53 `(ert-deftest ,name () ,@args
54 ;; These values were used by icalendar.el when tests were written:
55 (let ((icalendar-recurring-start-year 2005)
56 (icalendar-vcalendar-prodid "-//Emacs//NONSGML icalendar.el//EN")
57 (icalendar-uid-format "emacs%t%c")
58 (icalendar-export-hidden-diary-entries t))
59 ,@body))))
60
38;; ====================================================================== 61;; ======================================================================
39;; Helpers 62;; Helpers
40;; ====================================================================== 63;; ======================================================================
@@ -75,7 +98,7 @@
75;; Tests of functions 98;; Tests of functions
76;; ====================================================================== 99;; ======================================================================
77 100
78(ert-deftest icalendar--create-uid () 101(ical:deftest-obsolete icalendar--create-uid ()
79 "Test for `icalendar--create-uid'." 102 "Test for `icalendar--create-uid'."
80 (let* ((icalendar-uid-format "xxx-%c-%h-%u-%s") 103 (let* ((icalendar-uid-format "xxx-%c-%h-%u-%s")
81 (icalendar--uid-count 77) 104 (icalendar--uid-count 77)
@@ -92,7 +115,7 @@
92 (should (string= (concat "yyyDTSTARTyyy") 115 (should (string= (concat "yyyDTSTARTyyy")
93 (icalendar--create-uid entry-full contents))))) 116 (icalendar--create-uid entry-full contents)))))
94 117
95(ert-deftest icalendar-convert-anniversary-to-ical () 118(ical:deftest-obsolete icalendar-convert-anniversary-to-ical ()
96 "Test method for `icalendar--convert-anniversary-to-ical'." 119 "Test method for `icalendar--convert-anniversary-to-ical'."
97 (let* ((calendar-date-style 'iso) 120 (let* ((calendar-date-style 'iso)
98 result) 121 result)
@@ -106,7 +129,7 @@
106 (car result))) 129 (car result)))
107 (should (string= "g" (cdr result))))) 130 (should (string= "g" (cdr result)))))
108 131
109(ert-deftest icalendar--convert-cyclic-to-ical () 132(ical:deftest-obsolete icalendar--convert-cyclic-to-ical ()
110 "Test method for `icalendar--convert-cyclic-to-ical'." 133 "Test method for `icalendar--convert-cyclic-to-ical'."
111 (let* ((calendar-date-style 'iso) 134 (let* ((calendar-date-style 'iso)
112 result) 135 result)
@@ -119,7 +142,7 @@
119 (car result))) 142 (car result)))
120 (should (string= "Sommerferien" (cdr result))))) 143 (should (string= "Sommerferien" (cdr result)))))
121 144
122(ert-deftest icalendar--convert-block-to-ical () 145(ical:deftest-obsolete icalendar--convert-block-to-ical ()
123 "Test method for `icalendar--convert-block-to-ical'." 146 "Test method for `icalendar--convert-block-to-ical'."
124 (let* ((calendar-date-style 'iso) 147 (let* ((calendar-date-style 'iso)
125 result) 148 result)
@@ -132,7 +155,7 @@
132 (car result))) 155 (car result)))
133 (should (string= "Sommerferien" (cdr result))))) 156 (should (string= "Sommerferien" (cdr result)))))
134 157
135(ert-deftest icalendar--convert-float-to-ical () 158(ical:deftest-obsolete icalendar--convert-float-to-ical ()
136 "Test method for `icalendar--convert-float-to-ical'." 159 "Test method for `icalendar--convert-float-to-ical'."
137 ;; See Bug#78085 160 ;; See Bug#78085
138 (let* ((calendar-date-style 'iso) 161 (let* ((calendar-date-style 'iso)
@@ -148,7 +171,7 @@
148 (car result))) 171 (car result)))
149 (should (string= "1st Sat/month" (cdr result))))) 172 (should (string= "1st Sat/month" (cdr result)))))
150 173
151(ert-deftest icalendar--convert-yearly-to-ical () 174(ical:deftest-obsolete icalendar--convert-yearly-to-ical ()
152 "Test method for `icalendar--convert-yearly-to-ical'." 175 "Test method for `icalendar--convert-yearly-to-ical'."
153 (let* ((calendar-date-style 'iso) 176 (let* ((calendar-date-style 'iso)
154 result 177 result
@@ -164,7 +187,7 @@
164 (car result))) 187 (car result)))
165 (should (string= "Tag der Arbeit" (cdr result))))) 188 (should (string= "Tag der Arbeit" (cdr result)))))
166 189
167(ert-deftest icalendar--convert-weekly-to-ical () 190(ical:deftest-obsolete icalendar--convert-weekly-to-ical ()
168 "Test method for `icalendar--convert-weekly-to-ical'." 191 "Test method for `icalendar--convert-weekly-to-ical'."
169 (let* ((calendar-date-style 'iso) 192 (let* ((calendar-date-style 'iso)
170 result 193 result
@@ -179,7 +202,7 @@
179 (car result))) 202 (car result)))
180 (should (string= "subject" (cdr result))))) 203 (should (string= "subject" (cdr result)))))
181 204
182(ert-deftest icalendar--convert-sexp-to-ical () 205(ical:deftest-obsolete icalendar--convert-sexp-to-ical ()
183 "Test method for `icalendar--convert-sexp-to-ical'." 206 "Test method for `icalendar--convert-sexp-to-ical'."
184 (let* (result 207 (let* (result
185 (icalendar-export-sexp-enumeration-days 3)) 208 (icalendar-export-sexp-enumeration-days 3))
@@ -192,7 +215,7 @@
192 (should (string-match "Hebrew date (until sunset): .*" (cdr i)))) 215 (should (string-match "Hebrew date (until sunset): .*" (cdr i))))
193 result))) 216 result)))
194 217
195(ert-deftest icalendar--convert-to-ical () 218(ical:deftest-obsolete icalendar--convert-to-ical ()
196 "Test method for `icalendar--convert-to-ical'." 219 "Test method for `icalendar--convert-to-ical'."
197 (let* (result 220 (let* (result
198 (icalendar-export-sexp-enumerate-all t) 221 (icalendar-export-sexp-enumerate-all t)
@@ -216,7 +239,7 @@
216 (car (car result)))) 239 (car (car result))))
217 (should (string-match "Newton's birthday" (cdr (car result)))))) 240 (should (string-match "Newton's birthday" (cdr (car result))))))
218 241
219(ert-deftest icalendar--parse-vtimezone () 242(ical:deftest-obsolete icalendar--parse-vtimezone ()
220 "Test method for `icalendar--parse-vtimezone'." 243 "Test method for `icalendar--parse-vtimezone'."
221 (let (vtimezone result) 244 (let (vtimezone result)
222 ;; testcase: valid timezone with rrule 245 ;; testcase: valid timezone with rrule
@@ -290,7 +313,7 @@ END:VTIMEZONE
290 ;; FIXME: add testcase that covers changes for fix of bug#34315 313 ;; FIXME: add testcase that covers changes for fix of bug#34315
291 )) 314 ))
292 315
293(ert-deftest icalendar--convert-ordinary-to-ical () 316(ical:deftest-obsolete icalendar--convert-ordinary-to-ical ()
294 "Test method for `icalendar--convert-ordinary-to-ical'." 317 "Test method for `icalendar--convert-ordinary-to-ical'."
295 (let* ((calendar-date-style 'iso) 318 (let* ((calendar-date-style 'iso)
296 result) 319 result)
@@ -328,7 +351,7 @@ END:VTIMEZONE
328 (car result))) 351 (car result)))
329 (should (string= "s" (cdr result))))) 352 (should (string= "s" (cdr result)))))
330 353
331(ert-deftest icalendar--diarytime-to-isotime () 354(ical:deftest-obsolete icalendar--diarytime-to-isotime ()
332 "Test method for `icalendar--diarytime-to-isotime'." 355 "Test method for `icalendar--diarytime-to-isotime'."
333 (should (string= "T011500" 356 (should (string= "T011500"
334 (icalendar--diarytime-to-isotime "01:15" ""))) 357 (icalendar--diarytime-to-isotime "01:15" "")))
@@ -361,7 +384,7 @@ END:VTIMEZONE
361 (should (string= "T150000" 384 (should (string= "T150000"
362 (icalendar--diarytime-to-isotime "3" "pm")))) 385 (icalendar--diarytime-to-isotime "3" "pm"))))
363 386
364(ert-deftest icalendar--datetime-to-diary-date () 387(ical:deftest-obsolete icalendar--datetime-to-diary-date ()
365 "Test method for `icalendar--datetime-to-diary-date'." 388 "Test method for `icalendar--datetime-to-diary-date'."
366 (let* ((datetime '(59 59 23 31 12 2008)) 389 (let* ((datetime '(59 59 23 31 12 2008))
367 (calendar-date-style 'iso)) 390 (calendar-date-style 'iso))
@@ -374,7 +397,7 @@ END:VTIMEZONE
374 (should (string= "12 31 2008" 397 (should (string= "12 31 2008"
375 (icalendar--datetime-to-diary-date datetime))))) 398 (icalendar--datetime-to-diary-date datetime)))))
376 399
377(ert-deftest icalendar--datestring-to-isodate () 400(ical:deftest-obsolete icalendar--datestring-to-isodate ()
378 "Test method for `icalendar--datestring-to-isodate'." 401 "Test method for `icalendar--datestring-to-isodate'."
379 (let ((calendar-date-style 'iso)) 402 (let ((calendar-date-style 'iso))
380 ;; numeric iso 403 ;; numeric iso
@@ -427,7 +450,7 @@ END:VTIMEZONE
427 (icalendar--datestring-to-isodate "2021 Feb 11" nil 80))) 450 (icalendar--datestring-to-isodate "2021 Feb 11" nil 80)))
428 )) 451 ))
429 452
430(ert-deftest icalendar--first-weekday-of-year () 453(ical:deftest-obsolete icalendar--first-weekday-of-year ()
431 "Test method for `icalendar-first-weekday-of-year'." 454 "Test method for `icalendar-first-weekday-of-year'."
432 (should (eq 1 (icalendar-first-weekday-of-year "TU" 2008))) 455 (should (eq 1 (icalendar-first-weekday-of-year "TU" 2008)))
433 (should (eq 3 (icalendar-first-weekday-of-year "WE" 2007))) 456 (should (eq 3 (icalendar-first-weekday-of-year "WE" 2007)))
@@ -439,7 +462,7 @@ END:VTIMEZONE
439 (should (eq 3 (icalendar-first-weekday-of-year "MO" 2000))) 462 (should (eq 3 (icalendar-first-weekday-of-year "MO" 2000)))
440 (should (eq 1 (icalendar-first-weekday-of-year "TH" 1970)))) 463 (should (eq 1 (icalendar-first-weekday-of-year "TH" 1970))))
441 464
442(ert-deftest icalendar--import-format-sample () 465(ical:deftest-obsolete icalendar--import-format-sample ()
443 "Test method for `icalendar-import-format-sample'." 466 "Test method for `icalendar-import-format-sample'."
444 (should (string= (concat "SUMMARY='a' DESCRIPTION='b' LOCATION='c' " 467 (should (string= (concat "SUMMARY='a' DESCRIPTION='b' LOCATION='c' "
445 "ORGANIZER='d' STATUS='' URL='' CLASS=''") 468 "ORGANIZER='d' STATUS='' URL='' CLASS=''")
@@ -455,7 +478,7 @@ DESCRIPTION:b
455END:VEVENT 478END:VEVENT
456"))))) 479")))))
457 480
458(ert-deftest icalendar--format-ical-event () 481(ical:deftest-obsolete icalendar--format-ical-event ()
459 "Test `icalendar--format-ical-event'." 482 "Test `icalendar--format-ical-event'."
460 (let ((icalendar-import-format "%s%d%l%o%t%u%c") 483 (let ((icalendar-import-format "%s%d%l%o%t%u%c")
461 (icalendar-import-format-summary "SUM %s") 484 (icalendar-import-format-summary "SUM %s")
@@ -493,7 +516,7 @@ END:VEVENT
493 (should (string= "-sum-des-loc-org-nil-nil-nil-" 516 (should (string= "-sum-des-loc-org-nil-nil-nil-"
494 (icalendar--format-ical-event event))))) 517 (icalendar--format-ical-event event)))))
495 518
496(ert-deftest icalendar--parse-summary-and-rest () 519(ical:deftest-obsolete icalendar--parse-summary-and-rest ()
497 "Test `icalendar--parse-summary-and-rest'." 520 "Test `icalendar--parse-summary-and-rest'."
498 (let ((icalendar-import-format "%s%d%l%o%t%u%c") 521 (let ((icalendar-import-format "%s%d%l%o%t%u%c")
499 (icalendar-import-format-summary "SUM %s") 522 (icalendar-import-format-summary "SUM %s")
@@ -521,7 +544,7 @@ END:VEVENT
521 (should (not result)) 544 (should (not result))
522 )) 545 ))
523 546
524(ert-deftest icalendar--decode-isodatetime () 547(ical:deftest-obsolete icalendar--decode-isodatetime ()
525 "Test `icalendar--decode-isodatetime'." 548 "Test `icalendar--decode-isodatetime'."
526 (let ((tz (getenv "TZ"))) 549 (let ((tz (getenv "TZ")))
527 (unwind-protect 550 (unwind-protect
@@ -571,7 +594,7 @@ END:VEVENT
571 ;; restore time-zone even if something went terribly wrong 594 ;; restore time-zone even if something went terribly wrong
572 (setenv "TZ" tz)))) 595 (setenv "TZ" tz))))
573 596
574(ert-deftest icalendar--convert-tz-offset () 597(ical:deftest-obsolete icalendar--convert-tz-offset ()
575 "Test `icalendar--convert-tz-offset'." 598 "Test `icalendar--convert-tz-offset'."
576 (let ((tz (getenv "TZ"))) 599 (let ((tz (getenv "TZ")))
577 (unwind-protect 600 (unwind-protect
@@ -634,7 +657,7 @@ END:VEVENT
634 ;; restore time-zone even if something went terribly wrong 657 ;; restore time-zone even if something went terribly wrong
635 (setenv "TZ" tz)))) 658 (setenv "TZ" tz))))
636 659
637(ert-deftest icalendar--decode-isoduration () 660(ical:deftest-obsolete icalendar--decode-isoduration ()
638 "Test `icalendar--decode-isoduration'." 661 "Test `icalendar--decode-isoduration'."
639 662
640 ;; testcase: 7 days 663 ;; testcase: 7 days
@@ -764,7 +787,7 @@ END:VCALENDAR
764 ;; cleanup!! 787 ;; cleanup!!
765 (kill-buffer (find-buffer-visiting temp-file))))) 788 (kill-buffer (find-buffer-visiting temp-file)))))
766 789
767(ert-deftest icalendar-export-ordinary-no-time () 790(ical:deftest-obsolete icalendar-export-ordinary-no-time ()
768 "Perform export test." 791 "Perform export test."
769 792
770 (let ((icalendar-export-hidden-diary-entries nil)) 793 (let ((icalendar-export-hidden-diary-entries nil))
@@ -783,7 +806,7 @@ DTEND;VALUE=DATE:20001004
783SUMMARY:ordinary no time 806SUMMARY:ordinary no time
784")) 807"))
785 808
786(ert-deftest icalendar-export-ordinary () 809(ical:deftest-obsolete icalendar-export-ordinary ()
787 "Perform export test." 810 "Perform export test."
788 811
789 (icalendar-tests--test-export 812 (icalendar-tests--test-export
@@ -812,7 +835,7 @@ DTEND;VALUE=DATE-TIME:20001003T173000
812SUMMARY:ordinary with time 3 835SUMMARY:ordinary with time 3
813")) 836"))
814 837
815(ert-deftest icalendar-export-multiline () 838(ical:deftest-obsolete icalendar-export-multiline ()
816 "Perform export test." 839 "Perform export test."
817 840
818 ;; multiline -- FIXME!!! 841 ;; multiline -- FIXME!!!
@@ -830,7 +853,7 @@ DESCRIPTION:
830 17:30 multiline continued FIXME 853 17:30 multiline continued FIXME
831")) 854"))
832 855
833(ert-deftest icalendar-export-weekly-by-day () 856(ical:deftest-obsolete icalendar-export-weekly-by-day ()
834 "Perform export test." 857 "Perform export test."
835 858
836 ;; weekly by day 859 ;; weekly by day
@@ -854,7 +877,7 @@ RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO
854SUMMARY:weekly by day with start and end time 877SUMMARY:weekly by day with start and end time
855")) 878"))
856 879
857(ert-deftest icalendar-export-yearly () 880(ical:deftest-obsolete icalendar-export-yearly ()
858 "Perform export test." 881 "Perform export test."
859 ;; yearly 882 ;; yearly
860 (icalendar-tests--test-export 883 (icalendar-tests--test-export
@@ -867,7 +890,7 @@ RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=5;BYMONTHDAY=1
867SUMMARY:yearly no time 890SUMMARY:yearly no time
868")) 891"))
869 892
870(ert-deftest icalendar-export-anniversary () 893(ical:deftest-obsolete icalendar-export-anniversary ()
871 "Perform export test." 894 "Perform export test."
872 ;; anniversaries 895 ;; anniversaries
873 (icalendar-tests--test-export 896 (icalendar-tests--test-export
@@ -889,7 +912,7 @@ RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=10;BYMONTHDAY=03
889SUMMARY:anniversary with time 912SUMMARY:anniversary with time
890")) 913"))
891 914
892(ert-deftest icalendar-export-block () 915(ical:deftest-obsolete icalendar-export-block ()
893 "Perform export test." 916 "Perform export test."
894 ;; block 917 ;; block
895 (icalendar-tests--test-export 918 (icalendar-tests--test-export
@@ -919,7 +942,7 @@ RRULE:FREQ=DAILY;INTERVAL=1;UNTIL=20010706
919SUMMARY:block no end time 942SUMMARY:block no end time
920")) 943"))
921 944
922(ert-deftest icalendar-export-alarms () 945(ical:deftest-obsolete icalendar-export-alarms ()
923 "Perform export test with different settings for exporting alarms." 946 "Perform export test with different settings for exporting alarms."
924 ;; no alarm 947 ;; no alarm
925 (icalendar-tests--test-export 948 (icalendar-tests--test-export
@@ -1016,7 +1039,7 @@ END:VALARM
1016(defun icalendar-tests--diary-float (&rest args) 1039(defun icalendar-tests--diary-float (&rest args)
1017 (apply #'diary-float args)) 1040 (apply #'diary-float args))
1018 1041
1019(ert-deftest icalendar-export-bug-56241-dotted-pair () 1042(ical:deftest-obsolete icalendar-export-bug-56241-dotted-pair ()
1020 "See https://debbugs.gnu.org/cgi/bugreport.cgi?bug=56241#5" 1043 "See https://debbugs.gnu.org/cgi/bugreport.cgi?bug=56241#5"
1021 ;; This test started failing early July 2023 without any apparent change 1044 ;; This test started failing early July 2023 without any apparent change
1022 ;; to the underlying code, so is probably sensitive to the current date. 1045 ;; to the underlying code, so is probably sensitive to the current date.
@@ -1029,7 +1052,7 @@ END:VALARM
1029 "%%(icalendar-tests--diary-float 7 0 1) First Sunday in July 2")))) 1052 "%%(icalendar-tests--diary-float 7 0 1) First Sunday in July 2"))))
1030 1053
1031 1054
1032;; (ert-deftest icalendar-export-bug-56241-sexp-does-not-match () 1055;; (ical:deftest-obsolete icalendar-export-bug-56241-sexp-does-not-match ()
1033;; "Reported in #bug56241 -- needs to be fixed!" 1056;; "Reported in #bug56241 -- needs to be fixed!"
1034;; (let ((icalendar-export-sexp-enumeration-days 0)) 1057;; (let ((icalendar-export-sexp-enumeration-days 0))
1035;; (mapc (lambda (diary-string) 1058;; (mapc (lambda (diary-string)
@@ -1038,7 +1061,7 @@ END:VALARM
1038;; '("%%(diary-float 7 0 1) First Sunday in July 1" 1061;; '("%%(diary-float 7 0 1) First Sunday in July 1"
1039;; "%%(icalendar-tests--diary-float 7 0 1) First Sunday in July 2")))) 1062;; "%%(icalendar-tests--diary-float 7 0 1) First Sunday in July 2"))))
1040 1063
1041(ert-deftest icalendar-export-bug-56241-nested-sexps () 1064(ical:deftest-obsolete icalendar-export-bug-56241-nested-sexps ()
1042 "Reported in #bug56241 -- needs to be fixed!" 1065 "Reported in #bug56241 -- needs to be fixed!"
1043 (let ((icalendar-export-sexp-enumeration-days 366)) 1066 (let ((icalendar-export-sexp-enumeration-days 366))
1044 (mapc (lambda (diary-string) 1067 (mapc (lambda (diary-string)
@@ -1130,7 +1153,7 @@ Argument EXPECTED-OUTPUT file containing expected diary string."
1130 (should (string= expected-output result))) 1153 (should (string= expected-output result)))
1131 (kill-buffer (find-buffer-visiting temp-file))))) 1154 (kill-buffer (find-buffer-visiting temp-file)))))
1132 1155
1133(ert-deftest icalendar-import-non-recurring () 1156(ical:deftest-obsolete icalendar-import-non-recurring ()
1134 "Perform standard import tests." 1157 "Perform standard import tests."
1135 (icalendar-tests--test-import "import-non-recurring-1.ics" 1158 (icalendar-tests--test-import "import-non-recurring-1.ics"
1136 "import-non-recurring-1.diary-iso" 1159 "import-non-recurring-1.diary-iso"
@@ -1158,7 +1181,7 @@ Argument EXPECTED-OUTPUT file containing expected diary string."
1158 "import-non-recurring-another-example.diary-american")) 1181 "import-non-recurring-another-example.diary-american"))
1159 1182
1160 1183
1161(ert-deftest icalendar-import-rrule () 1184(ical:deftest-obsolete icalendar-import-rrule ()
1162 (icalendar-tests--test-import "import-rrule-daily.ics" 1185 (icalendar-tests--test-import "import-rrule-daily.ics"
1163 "import-rrule-daily.diary-iso" 1186 "import-rrule-daily.diary-iso"
1164 "import-rrule-daily.diary-european" 1187 "import-rrule-daily.diary-european"
@@ -1217,7 +1240,7 @@ Argument EXPECTED-OUTPUT file containing expected diary string."
1217 "import-rrule-count-every-second-year.diary-american") 1240 "import-rrule-count-every-second-year.diary-american")
1218 ) 1241 )
1219 1242
1220(ert-deftest icalendar-import-duration () 1243(ical:deftest-obsolete icalendar-import-duration ()
1221 (icalendar-tests--test-import "import-duration.ics" 1244 (icalendar-tests--test-import "import-duration.ics"
1222 "import-duration.diary-iso" 1245 "import-duration.diary-iso"
1223 "import-duration.diary-european" 1246 "import-duration.diary-european"
@@ -1228,41 +1251,41 @@ Argument EXPECTED-OUTPUT file containing expected diary string."
1228 "import-duration-2.diary-european" 1251 "import-duration-2.diary-european"
1229 "import-duration-2.diary-american")) 1252 "import-duration-2.diary-american"))
1230 1253
1231(ert-deftest icalendar-import-bug-6766 () 1254(ical:deftest-obsolete icalendar-import-bug-6766 ()
1232 ;;bug#6766 -- multiple byday values in a weekly rrule 1255 ;;bug#6766 -- multiple byday values in a weekly rrule
1233 (icalendar-tests--test-import "import-bug-6766.ics" 1256 (icalendar-tests--test-import "import-bug-6766.ics"
1234 "import-bug-6766.diary-iso" 1257 "import-bug-6766.diary-iso"
1235 "import-bug-6766.diary-european" 1258 "import-bug-6766.diary-european"
1236 "import-bug-6766.diary-american")) 1259 "import-bug-6766.diary-american"))
1237 1260
1238(ert-deftest icalendar-import-bug-24199 () 1261(ical:deftest-obsolete icalendar-import-bug-24199 ()
1239 ;;bug#24199 -- monthly rule with byday-clause 1262 ;;bug#24199 -- monthly rule with byday-clause
1240 (icalendar-tests--test-import "import-bug-24199.ics" 1263 (icalendar-tests--test-import "import-bug-24199.ics"
1241 "import-bug-24199.diary-iso" 1264 "import-bug-24199.diary-iso"
1242 "import-bug-24199.diary-european" 1265 "import-bug-24199.diary-european"
1243 "import-bug-24199.diary-american")) 1266 "import-bug-24199.diary-american"))
1244 1267
1245(ert-deftest icalendar-import-bug-33277 () 1268(ical:deftest-obsolete icalendar-import-bug-33277 ()
1246 ;;bug#33277 -- start time equals end time 1269 ;;bug#33277 -- start time equals end time
1247 (icalendar-tests--test-import "import-bug-33277.ics" 1270 (icalendar-tests--test-import "import-bug-33277.ics"
1248 "import-bug-33277.diary-iso" 1271 "import-bug-33277.diary-iso"
1249 "import-bug-33277.diary-european" 1272 "import-bug-33277.diary-european"
1250 "import-bug-33277.diary-american")) 1273 "import-bug-33277.diary-american"))
1251 1274
1252(ert-deftest icalendar-import-multiple-vcalendars () 1275(ical:deftest-obsolete icalendar-import-multiple-vcalendars ()
1253 (icalendar-tests--test-import "import-multiple-vcalendars.ics" 1276 (icalendar-tests--test-import "import-multiple-vcalendars.ics"
1254 "import-multiple-vcalendars.diary-iso" 1277 "import-multiple-vcalendars.diary-iso"
1255 "import-multiple-vcalendars.diary-european" 1278 "import-multiple-vcalendars.diary-european"
1256 "import-multiple-vcalendars.diary-american")) 1279 "import-multiple-vcalendars.diary-american"))
1257 1280
1258(ert-deftest icalendar-import-with-uid () 1281(ical:deftest-obsolete icalendar-import-with-uid ()
1259 "Perform import test with uid." 1282 "Perform import test with uid."
1260 (icalendar-tests--test-import "import-with-uid.ics" 1283 (icalendar-tests--test-import "import-with-uid.ics"
1261 "import-with-uid.diary-iso" 1284 "import-with-uid.diary-iso"
1262 "import-with-uid.diary-european" 1285 "import-with-uid.diary-european"
1263 "import-with-uid.diary-american")) 1286 "import-with-uid.diary-american"))
1264 1287
1265(ert-deftest icalendar-import-with-timezone () 1288(ical:deftest-obsolete icalendar-import-with-timezone ()
1266 ;; This is known to fail on MS-Windows, because the test assumes 1289 ;; This is known to fail on MS-Windows, because the test assumes
1267 ;; Posix features of specifying DST rules. 1290 ;; Posix features of specifying DST rules.
1268 :expected-result (if (memq system-type '(windows-nt ms-dos)) 1291 :expected-result (if (memq system-type '(windows-nt ms-dos))
@@ -1334,7 +1357,7 @@ Argument INPUT icalendar event string."
1334 (set-buffer-modified-p nil) 1357 (set-buffer-modified-p nil)
1335 (kill-buffer (current-buffer)))))))) 1358 (kill-buffer (current-buffer))))))))
1336 1359
1337(ert-deftest icalendar-cycle () 1360(ical:deftest-obsolete icalendar-cycle ()
1338 "Perform cycling tests. 1361 "Perform cycling tests.
1339Take care to avoid auto-generated UIDs here." 1362Take care to avoid auto-generated UIDs here."
1340 (icalendar-tests--test-cycle 1363 (icalendar-tests--test-cycle
@@ -1363,7 +1386,7 @@ SUMMARY:and diary-anniversary
1363;; ====================================================================== 1386;; ======================================================================
1364;; Real world 1387;; Real world
1365;; ====================================================================== 1388;; ======================================================================
1366(ert-deftest icalendar-real-world () 1389(ical:deftest-obsolete icalendar-real-world ()
1367 "Perform real-world tests, as gathered from problem reports." 1390 "Perform real-world tests, as gathered from problem reports."
1368 ;; This is known to fail on MS-Windows, since it doesn't support DST 1391 ;; This is known to fail on MS-Windows, since it doesn't support DST
1369 ;; specification with month and day. 1392 ;; specification with month and day.
@@ -1506,7 +1529,7 @@ RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=09;BYMONTHDAY=21
1506SUMMARY:ff birthday (%d years old)") 1529SUMMARY:ff birthday (%d years old)")
1507 1530
1508 ;; FIXME: this testcase verifies that icalendar-export fails to 1531 ;; FIXME: this testcase verifies that icalendar-export fails to
1509 ;; export the nested sexp. After repairing bug56241 icalendar-export 1532 ;; export the nested sexp. After repairing bug56241 icalendar-export
1510 ;; works correctly for this sexp but now the testcase fails. 1533 ;; works correctly for this sexp but now the testcase fails.
1511 ;; Therefore this testcase is disabled for the time being. 1534 ;; Therefore this testcase is disabled for the time being.
1512 ;; (icalendar-tests--test-export 1535 ;; (icalendar-tests--test-export
@@ -1702,7 +1725,7 @@ SUMMARY:NNN Wwwwwwww Wwwww - Aaaaaa Pppppppp rrrrrr ddd oo Nnnnnnnn 30
1702 (let ((time (icalendar--decode-isodatetime string day zone))) 1725 (let ((time (icalendar--decode-isodatetime string day zone)))
1703 (format-time-string "%FT%T%z" (encode-time time) 0))) 1726 (format-time-string "%FT%T%z" (encode-time time) 0)))
1704 1727
1705(ert-deftest icalendar-tests--decode-isodatetime () 1728(ical:deftest-obsolete icalendar-tests--decode-isodatetime ()
1706 "Test `icalendar--decode-isodatetime'." 1729 "Test `icalendar--decode-isodatetime'."
1707 (should (equal (icalendar-test--format "20040917T050910-02:00") 1730 (should (equal (icalendar-test--format "20040917T050910-02:00")
1708 "2004-09-17T03:09:10+0000")) 1731 "2004-09-17T03:09:10+0000"))
@@ -1728,4 +1751,8 @@ SUMMARY:NNN Wwwwwwww Wwwww - Aaaaaa Pppppppp rrrrrr ddd oo Nnnnnnnn 30
1728 "2004-09-17T06:09:10+0000"))) 1751 "2004-09-17T06:09:10+0000")))
1729 1752
1730(provide 'icalendar-tests) 1753(provide 'icalendar-tests)
1754;; Local Variables:
1755;; read-symbol-shorthands: (("ical:" . "icalendar-"))
1756;; byte-compile-warnings: (not obsolete)
1757;; End:
1731;;; icalendar-tests.el ends here 1758;;; icalendar-tests.el ends here
diff --git a/test/lisp/misc-tests.el b/test/lisp/misc-tests.el
index b6f5f01ad2a..81ebe1a5869 100644
--- a/test/lisp/misc-tests.el
+++ b/test/lisp/misc-tests.el
@@ -25,6 +25,7 @@
25 25
26(require 'ert) 26(require 'ert)
27(require 'misc) 27(require 'misc)
28(require 'mule-util)
28 29
29(defmacro with-misc-test (original result &rest body) 30(defmacro with-misc-test (original result &rest body)
30 (declare (indent 2)) 31 (declare (indent 2))
@@ -243,5 +244,94 @@
243 (setq-default display-line-numbers dln)) 244 (setq-default display-line-numbers dln))
244 (should (= w0 w1)))) 245 (should (= w0 w1))))
245 246
247;; Exercise `truncate-string-pixelwise' with strings of the same
248;; characters of differing widths, with and without ellipses, in varying
249;; faces, and varying face heights and compare results to each
250;; character's measured width.
251(ert-deftest misc-test-truncate-string-pixelwise ()
252 ;; Test empty string without an explicit buffer.
253 (should (equal (truncate-string-pixelwise "" 123) ""))
254 ;; Test fast path without an explicit buffer.
255 (should (equal (truncate-string-pixelwise "123" 123) "123"))
256 (with-temp-buffer
257 ;; Test empty string with an explicit buffer.
258 (should (equal (truncate-string-pixelwise "" 123 (current-buffer)) ""))
259 ;; Test fast path with an explicit buffer.
260 (should (equal (truncate-string-pixelwise "123" 123 (current-buffer)) "123")))
261
262 (dolist (c '(?W ?X ?y ?1))
263 (dolist (ellipsis `(nil "..." ,(truncate-string-ellipsis)))
264 (dolist (face '(fixed-pitch variable-pitch))
265 (dolist (height '(1.0 0.5 1.5))
266 (with-temp-buffer
267 (setq-local face-remapping-alist `((,face . default)))
268 (face-remap-add-relative 'default :height height)
269 (let ((char-pixels (string-pixel-width
270 (make-string 1 c) (current-buffer))))
271 (dotimes (i 20)
272 (setq i (1+ i))
273 (should (eq i (length
274 (truncate-string-pixelwise
275 (make-string (* i 2) c)
276 (* i char-pixels)
277 (current-buffer)
278 ellipsis))))))))))))
279
280;; Exercise `truncate-string-pixelwise' with varying unicode strings, in
281;; varying faces, and varying face heights and compare results to a
282;; naive `string-pixel-width' based string truncate function.
283(ert-deftest misc-test-truncate-string-pixelwise-unicode ()
284 :tags '(:expensive-test)
285 (skip-when noninteractive)
286 (let ((max-pixels 500)
287 (truncate-string-naive (lambda (string pixels buffer)
288 (while (and (length> string 0)
289 (> (string-pixel-width string buffer) pixels))
290 (setq string (substring string 0 (1- (length string)))))
291 string))
292 (strings (list
293 "foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz foo bar baz"
294 (concat "話說天下大勢,分久必合,合久必分:周末七國分爭,并入於秦。"
295 "及秦滅之後,楚、漢分爭,又并入於漢。漢朝自高祖斬白蛇而起義,"
296 "一統天下。後來光武中興,傳至獻帝,遂分為三國。推其致亂之由,"
297 "殆始於桓、靈二帝。桓帝禁錮善類,崇信宦官。及桓帝崩,靈帝即位,"
298 "大將軍竇武、太傅陳蕃,共相輔佐。時有宦官曹節等弄權,竇武、陳蕃謀誅之,"
299 "作事不密,反為所害。中涓自此愈橫")
300 (concat "короче теперь если по русски написать все четко или все равно"
301 " короче теперь если по русски написать все четко или все равно"
302 " короче теперь если по русски написать все четко или все равно"
303 " короче теперь если по русски написать все четко или все равно")
304 "будет разрыв строки непонятно где🏁🚩🎌🏴🏳️ 🏳️ <200d>🌈🏳️ <200d>⚧️🏴<200d>☠️"
305 (apply #'concat (make-list 200 "\u0065\u0301 ")) ; composed é \u00E9
306 (let ((woman-loves-man ; 👩‍❤️‍👨
307 (concat "\N{WOMAN}"
308 "\N{ZERO WIDTH JOINER}"
309 "\N{HEAVY BLACK HEART}"
310 "\N{VARIATION SELECTOR-16}"
311 "\N{ZERO WIDTH JOINER}"
312 "\N{MAN}"
313 " ")))
314 (apply #'concat (make-list 200 woman-loves-man)))
315 (propertize (let ((varying-height-string
316 (mapconcat
317 #'identity
318 (list "AWi!"
319 (propertize "foo" 'face '(:height 2.5))
320 (propertize "bar" 'face '(:height 0.5))
321 (propertize "baz" 'face '(:height 1.0)))
322 " ")))
323 (apply #'concat (make-list 100 varying-height-string)))
324 'face 'variable-pitch))))
325 (dolist (face '(fixed-pitch variable-pitch))
326 (dolist (height '(1.0 0.5 1.5))
327 (with-temp-buffer
328 (setq-local face-remapping-alist `((,face . default)))
329 (face-remap-add-relative 'default :height height)
330 (dolist (string strings)
331 (should (eq (length (funcall truncate-string-naive
332 string max-pixels (current-buffer)))
333 (length (truncate-string-pixelwise
334 string max-pixels (current-buffer)))))))))))
335
246(provide 'misc-tests) 336(provide 'misc-tests)
247;;; misc-tests.el ends here 337;;; misc-tests.el ends here
diff --git a/test/lisp/net/dbus-tests.el b/test/lisp/net/dbus-tests.el
index 53ce1929cad..3d0ab522d3f 100644
--- a/test/lisp/net/dbus-tests.el
+++ b/test/lisp/net/dbus-tests.el
@@ -28,10 +28,9 @@
28(defvar dbus-debug) 28(defvar dbus-debug)
29(defvar dbus-message-type-signal) 29(defvar dbus-message-type-signal)
30(declare-function dbus-get-unique-name "dbusbind.c" (bus)) 30(declare-function dbus-get-unique-name "dbusbind.c" (bus))
31(declare-function dbus-close-inhibitor-lock "dbusbind.c" (lock)) 31(declare-function dbus--fd-open "dbusbind.c" (filename))
32(declare-function dbus-registered-inhibitor-locks "dbusbind.c" ()) 32(declare-function dbus--fd-close "dbusbind.c" (fd))
33(declare-function dbus-make-inhibitor-lock "dbusbind.c" 33(declare-function dbus--registered-fds "dbusbind.c" ())
34 (what why &optional block))
35 34
36(defconst dbus--test-enabled-session-bus 35(defconst dbus--test-enabled-session-bus
37 (and (featurep 'dbusbind) 36 (and (featurep 'dbusbind)
@@ -2308,89 +2307,156 @@ The argument EXPECTED-ARGS is a list of expected arguments for the method."
2308 ;; Cleanup. 2307 ;; Cleanup.
2309 (dbus-unregister-service :session dbus--test-service))) 2308 (dbus-unregister-service :session dbus--test-service)))
2310 2309
2311(ert-deftest dbus-test10-inhibitor-locks () 2310(ert-deftest dbus-test10-keep-fd ()
2312 "Check `dbus-*-inhibitor-locks'." 2311 "Check D-Bus `:keep-fd' argument."
2313 :tags '(:expensive-test) 2312 :tags '(:expensive-test)
2314 (skip-unless dbus--test-enabled-system-bus) 2313 (skip-unless dbus--test-enabled-system-bus)
2315 (skip-unless (dbus-ping :system dbus--test-systemd-service 1000)) 2314 (skip-unless (dbus-ping :system dbus--test-systemd-service 1000))
2316 2315
2317 (let (lock1 lock2) 2316 (let ((what "sleep")
2317 (who "Emacs test user")
2318 (why "Test delay")
2319 (mode "delay")
2320 (fd-directory (format "/proc/%d/fd" (emacs-pid)))
2321 lock1 lock2)
2318 ;; Create inhibitor lock. 2322 ;; Create inhibitor lock.
2319 (setq lock1 (dbus-make-inhibitor-lock "sleep" "Test delay")) 2323 (setq lock1
2324 (dbus-call-method
2325 :system dbus--test-systemd-service dbus--test-systemd-path
2326 dbus--test-systemd-manager-interface "Inhibit"
2327 what who why mode))
2320 (should (natnump lock1)) 2328 (should (natnump lock1))
2321 ;; The lock is reported by systemd. 2329 ;; The lock is reported by systemd.
2322 (should 2330 (should
2323 (member 2331 (member
2324 (list "sleep" "Emacs" "Test delay" "delay" (user-uid) (emacs-pid)) 2332 (list what who why mode (user-uid) (emacs-pid))
2325 (dbus-call-method 2333 (dbus-call-method
2326 :system dbus--test-systemd-service dbus--test-systemd-path 2334 :system dbus--test-systemd-service dbus--test-systemd-path
2327 dbus--test-systemd-manager-interface "ListInhibitors"))) 2335 dbus--test-systemd-manager-interface "ListInhibitors")))
2328 ;; The lock is registered internally. 2336 ;; The lock is not registered internally.
2329 (should 2337 (should-not (assoc lock1 (dbus--registered-fds)))
2330 (member
2331 (list lock1 "sleep" "Test delay" nil)
2332 (dbus-registered-inhibitor-locks)))
2333 ;; There exist a file descriptor. 2338 ;; There exist a file descriptor.
2334 (when (file-directory-p (format "/proc/%d/fd" (emacs-pid))) 2339 (when (file-directory-p fd-directory)
2335 (should (file-symlink-p (format "/proc/%d/fd/%d" (emacs-pid) lock1)))) 2340 (should
2336 2341 (file-symlink-p
2337 ;; It is not possible to modify registered inhibitor locks on Lisp level. 2342 (expand-file-name (number-to-string lock1) fd-directory))))
2338 (setcar (assoc lock1 (dbus-registered-inhibitor-locks)) 'malicious)
2339 (should (assoc lock1 (dbus-registered-inhibitor-locks)))
2340 (should-not (assoc 'malicious (dbus-registered-inhibitor-locks)))
2341
2342 ;; Creating it again returns the same inhibitor lock.
2343 (should (= lock1 (dbus-make-inhibitor-lock "sleep" "Test delay")))
2344 2343
2345 ;; Create another inhibitor lock. 2344 ;; Create another inhibitor lock. Keep the file descriptor.
2346 (setq lock2 (dbus-make-inhibitor-lock "sleep" "Test block" 'block)) 2345 (setq lock2
2346 (dbus-call-method
2347 :system dbus--test-systemd-service dbus--test-systemd-path
2348 dbus--test-systemd-manager-interface "Inhibit" :keep-fd
2349 what who why mode))
2347 (should (natnump lock2)) 2350 (should (natnump lock2))
2348 (should-not (= lock1 lock2)) 2351 (should-not (= lock1 lock2))
2349 ;; The lock is reported by systemd. 2352 ;; The lock is reported by systemd.
2350 (should 2353 (should
2351 (member 2354 (member
2352 (list "sleep" "Emacs" "Test block" "block" (user-uid) (emacs-pid)) 2355 (list what who why mode (user-uid) (emacs-pid))
2353 (dbus-call-method 2356 (dbus-call-method
2354 :system dbus--test-systemd-service dbus--test-systemd-path 2357 :system dbus--test-systemd-service dbus--test-systemd-path
2355 dbus--test-systemd-manager-interface "ListInhibitors"))) 2358 dbus--test-systemd-manager-interface "ListInhibitors")))
2356 ;; The lock is registered internally. 2359 ;; The lock is registered internally.
2357 (should 2360 (should
2358 (member 2361 (member
2359 (list lock2 "sleep" "Test block" t) 2362 (cons lock2 dbus--test-systemd-path)
2360 (dbus-registered-inhibitor-locks))) 2363 (dbus--registered-fds)))
2361 ;; There exist a file descriptor. 2364 ;; There exist a file descriptor.
2362 (when (file-directory-p (format "/proc/%d/fd" (emacs-pid))) 2365 (when (file-directory-p fd-directory)
2363 (should (file-symlink-p (format "/proc/%d/fd/%d" (emacs-pid) lock2)))) 2366 (should
2364 2367 (file-symlink-p
2365 ;; Close the first inhibitor lock. 2368 (expand-file-name (number-to-string lock2) fd-directory))))
2366 (should (dbus-close-inhibitor-lock lock1)) 2369
2367 ;; The internal registration has gone. 2370 ;; Create another inhibitor lock via
2368 (should-not 2371 ;; `dbus-call-method-asynchronously'. Keep the file descriptor.
2369 (member 2372 (setq lock1 nil)
2370 (list lock1 "sleep" "Test delay" nil) 2373 (dbus-call-method-asynchronously
2371 (dbus-registered-inhibitor-locks))) 2374 :system dbus--test-systemd-service dbus--test-systemd-path
2372 ;; The file descriptor has been deleted. 2375 dbus--test-systemd-manager-interface "Inhibit"
2373 (when (file-directory-p (format "/proc/%d/fd" (emacs-pid))) 2376 (lambda (lock) (setq lock1 lock)) :keep-fd
2374 (should-not (file-symlink-p (format "/proc/%d/fd/%d" (emacs-pid) lock1)))) 2377 what who why mode)
2375 2378 (with-timeout (1 (dbus--test-timeout-handler))
2376 ;; Closing it again is a noop. 2379 (while (null lock1) (read-event nil nil 0.1)))
2377 (should-not (dbus-close-inhibitor-lock lock1))
2378
2379 ;; Creating it again returns (another?) inhibitor lock.
2380 (setq lock1 (dbus-make-inhibitor-lock "sleep" "Test delay"))
2381 (should (natnump lock1)) 2380 (should (natnump lock1))
2381 (should-not (= lock1 lock2))
2382 ;; The lock is registered internally. 2382 ;; The lock is registered internally.
2383 (should 2383 (should
2384 (member 2384 (member
2385 (list lock1 "sleep" "Test delay" nil) 2385 (cons lock1 dbus--test-systemd-path)
2386 (dbus-registered-inhibitor-locks))) 2386 (dbus--registered-fds)))
2387 ;; There exist a file descriptor. 2387 ;; There exist a file descriptor.
2388 (when (file-directory-p (format "/proc/%d/fd" (emacs-pid))) 2388 (when (file-directory-p fd-directory)
2389 (should (file-symlink-p (format "/proc/%d/fd/%d" (emacs-pid) lock1)))) 2389 (should
2390 (file-symlink-p
2391 (expand-file-name (number-to-string lock1) fd-directory))))
2392
2393 ;; It is not possible to modify registered inhibitor locks on Lisp level.
2394 (setcar (assoc lock1 (dbus--registered-fds)) 'malicious)
2395 (should (assoc lock1 (dbus--registered-fds)))
2396 (should-not (assoc 'malicious (dbus--registered-fds)))
2390 2397
2391 ;; Close the inhibitor locks. 2398 ;; Close the inhibitor locks.
2392 (should (dbus-close-inhibitor-lock lock1)) 2399 (should (dbus--fd-close lock1))
2393 (should (dbus-close-inhibitor-lock lock2)))) 2400 (should (dbus--fd-close lock2))
2401 ;; The internal registration has gone.
2402 (should-not
2403 (member
2404 (cons lock1 dbus--test-systemd-path)
2405 (dbus--registered-fds)))
2406 (should-not
2407 (member
2408 (cons lock2 dbus--test-systemd-path)
2409 (dbus--registered-fds)))
2410 ;; The file descriptors have been deleted.
2411 (when (file-directory-p fd-directory)
2412 (should-not
2413 (file-exists-p (expand-file-name (number-to-string lock1) fd-directory)))
2414 (should-not
2415 (file-exists-p (expand-file-name (number-to-string lock2) fd-directory))))
2416
2417 ;; Closing them again is a noop.
2418 (should-not (dbus--fd-close lock1))
2419 (should-not (dbus--fd-close lock2))))
2420
2421(ert-deftest dbus-test10-open-close-fd ()
2422 "Check D-Bus open/close a file descriptor."
2423 :tags '(:expensive-test)
2424 (skip-unless dbus--test-enabled-system-bus)
2425 (skip-unless (dbus-ping :system dbus--test-systemd-service 1000))
2426
2427 (ert-with-temp-file tmpfile
2428 (let ((fd-directory (format "/proc/%d/fd" (emacs-pid)))
2429 fd)
2430 ;; Create file descriptor.
2431 (setq fd (dbus--fd-open tmpfile))
2432 (should (natnump fd))
2433 ;; The file descriptor is registered internally.
2434 (should (member (cons fd tmpfile) (dbus--registered-fds)))
2435 ;; There exist a file descriptor file.
2436 (when (file-directory-p fd-directory)
2437 (should
2438 (file-symlink-p (expand-file-name (number-to-string fd) fd-directory)))
2439 (should
2440 (string-equal
2441 (file-truename (expand-file-name (number-to-string fd) fd-directory))
2442 tmpfile)))
2443
2444 ;; It is not possible to modify registered file descriptors on Lisp level.
2445 (setcar (assoc fd (dbus--registered-fds)) 'malicious)
2446 (should (assoc fd (dbus--registered-fds)))
2447 (should-not (assoc 'malicious (dbus--registered-fds)))
2448
2449 ;; Close the file descriptor.
2450 (should (dbus--fd-close fd))
2451 ;; The internal registration has gone.
2452 (should-not (member (cons fd tmpfile) (dbus--registered-fds)))
2453 ;; The file descriptor file has been deleted.
2454 (when (file-directory-p fd-directory)
2455 (should-not
2456 (file-exists-p (expand-file-name (number-to-string fd) fd-directory))))
2457
2458 ;; Closing it again is a noop.
2459 (should-not (dbus--fd-close fd)))))
2394 2460
2395(defun dbus-test-all (&optional interactive) 2461(defun dbus-test-all (&optional interactive)
2396 "Run all tests for \\[dbus]." 2462 "Run all tests for \\[dbus]."
diff --git a/test/lisp/net/tramp-archive-tests.el b/test/lisp/net/tramp-archive-tests.el
index f3bfaac005c..79fbe38b299 100644
--- a/test/lisp/net/tramp-archive-tests.el
+++ b/test/lisp/net/tramp-archive-tests.el
@@ -757,7 +757,7 @@ This tests also `file-executable-p', `file-writable-p' and `set-file-modes'."
757 ;; `set-file-modes' is not implemented. 757 ;; `set-file-modes' is not implemented.
758 (should-error 758 (should-error
759 (set-file-modes tmp-name1 #o777) 759 (set-file-modes tmp-name1 #o777)
760 :type 'file-error) 760 :type 'remote-file-error)
761 (should (= (file-modes tmp-name1) #o400)) 761 (should (= (file-modes tmp-name1) #o400))
762 (should-not (file-executable-p tmp-name1)) 762 (should-not (file-executable-p tmp-name1))
763 (should-not (file-writable-p tmp-name1)) 763 (should-not (file-writable-p tmp-name1))
@@ -766,7 +766,7 @@ This tests also `file-executable-p', `file-writable-p' and `set-file-modes'."
766 ;; `set-file-modes' is not implemented. 766 ;; `set-file-modes' is not implemented.
767 (should-error 767 (should-error
768 (set-file-modes tmp-name2 #o777) 768 (set-file-modes tmp-name2 #o777)
769 :type 'file-error) 769 :type 'remote-file-error)
770 (should (= (file-modes tmp-name2) #o500)) 770 (should (= (file-modes tmp-name2) #o500))
771 (should (file-executable-p tmp-name2)) 771 (should (file-executable-p tmp-name2))
772 (should-not (file-writable-p tmp-name2))) 772 (should-not (file-writable-p tmp-name2)))
@@ -796,7 +796,7 @@ This tests also `file-executable-p', `file-writable-p' and `set-file-modes'."
796 ;; `make-symbolic-link' is not implemented. 796 ;; `make-symbolic-link' is not implemented.
797 (should-error 797 (should-error
798 (make-symbolic-link tmp-name1 tmp-name2) 798 (make-symbolic-link tmp-name1 tmp-name2)
799 :type 'file-error) 799 :type 'remote-file-error)
800 (should (file-symlink-p tmp-name2)) 800 (should (file-symlink-p tmp-name2))
801 (should (file-regular-p tmp-name2)) 801 (should (file-regular-p tmp-name2))
802 (should 802 (should
diff --git a/test/lisp/net/tramp-tests.el b/test/lisp/net/tramp-tests.el
index bbfe15d2f59..28d773ca616 100644
--- a/test/lisp/net/tramp-tests.el
+++ b/test/lisp/net/tramp-tests.el
@@ -71,6 +71,7 @@
71(declare-function edebug-mode "edebug") 71(declare-function edebug-mode "edebug")
72(declare-function project-mode-line-format "project") 72(declare-function project-mode-line-format "project")
73(declare-function tramp-check-remote-uname "tramp-sh") 73(declare-function tramp-check-remote-uname "tramp-sh")
74(declare-function tramp-file-name-with-sudo "tramp-cmds")
74(declare-function tramp-find-executable "tramp-sh") 75(declare-function tramp-find-executable "tramp-sh")
75(declare-function tramp-get-remote-chmod-h "tramp-sh") 76(declare-function tramp-get-remote-chmod-h "tramp-sh")
76(declare-function tramp-get-remote-path "tramp-sh") 77(declare-function tramp-get-remote-path "tramp-sh")
@@ -185,7 +186,7 @@ The temporary file is not created."
185 (declare (indent defun) (debug (body))) 186 (declare (indent defun) (debug (body)))
186 `(condition-case err 187 `(condition-case err
187 (progn ,@body) 188 (progn ,@body)
188 (file-error 189 (remote-file-error
189 (unless (string-match-p 190 (unless (string-match-p
190 (rx bol (| "make-symbolic-link not supported" 191 (rx bol (| "make-symbolic-link not supported"
191 (: "Making symbolic link" 192 (: "Making symbolic link"
@@ -2203,19 +2204,31 @@ being the result.")
2203 m)) 2204 m))
2204 :type 'user-error))))) 2205 :type 'user-error)))))
2205 2206
2206(ert-deftest tramp-test03-file-name-method-rules () 2207(ert-deftest tramp-test03-file-error ()
2207 "Check file name rules for some methods." 2208 "Check that Tramp signals an error in case of connection problems."
2208 (skip-unless (eq tramp-syntax 'default)) 2209 ;; Connect to a non-existing host.
2209 (skip-unless (tramp--test-enabled)) 2210 (let ((vec (copy-tramp-file-name tramp-test-vec))
2210 2211 ;; Don't poison it.
2211 ;; Multi hops are allowed for inline methods only. 2212 (tramp-default-proxies-alist tramp-default-proxies-alist)
2212 (let (non-essential) 2213 (tramp-show-ad-hoc-proxies t))
2213 (should-error 2214 (cl-letf* (((symbol-function #'read-string) #'ignore) ; Suppress password.
2214 (expand-file-name "/ssh:user1@host1|method:user2@host2:/path/to/file") 2215 ((tramp-file-name-host vec) "example.com.invalid"))
2215 :type 'user-error) 2216 (should-error
2216 (should-error 2217 (file-exists-p (tramp-make-tramp-file-name vec))
2217 (expand-file-name "/method:user1@host1|ssh:user2@host2:/path/to/file") 2218 ;; `user-error' is raised if the host shall be local.
2218 :type 'user-error))) 2219 ;; `remote-file-error' is raised if the host cannot be connected.
2220 :type (if (tramp--test-ange-ftp-p)
2221 'ftp-error '(user-error remote-file-error)))
2222 (should-error
2223 (file-exists-p (tramp-make-tramp-file-name vec))
2224 ;; `ftp-error' and `remote-file-error' are subcategories of
2225 ;; `file-error'. Let's check this as well.
2226 :type '(user-error file-error))
2227 ;; Check multi-hop.
2228 (should-error
2229 (file-exists-p
2230 (tramp-file-name-with-sudo (tramp-make-tramp-file-name vec)))
2231 :type '(user-error file-error)))))
2219 2232
2220(ert-deftest tramp-test04-substitute-in-file-name () 2233(ert-deftest tramp-test04-substitute-in-file-name ()
2221 "Check `substitute-in-file-name'." 2234 "Check `substitute-in-file-name'."
@@ -7637,11 +7650,12 @@ This requires restrictions of file name syntax."
7637 (unless (tramp--test-crypt-p) 7650 (unless (tramp--test-crypt-p)
7638 (or (tramp--test-adb-p) (tramp--test-sh-p) (tramp--test-sshfs-p) 7651 (or (tramp--test-adb-p) (tramp--test-sh-p) (tramp--test-sshfs-p)
7639 (and (tramp--test-smb-p) 7652 (and (tramp--test-smb-p)
7640 (file-writable-p 7653 (ignore-errors
7641 (file-name-concat 7654 (file-writable-p
7642 (file-remote-p ert-remote-temporary-file-directory) 7655 (file-name-concat
7643 ;; We check a directory on the "ADMIN$" share. 7656 (file-remote-p ert-remote-temporary-file-directory)
7644 "ADMIN$" "Boot")))))) 7657 ;; We check a directory on the "ADMIN$" share.
7658 "ADMIN$" "Boot")))))))
7645 7659
7646(defun tramp--test-supports-set-file-modes-p () 7660(defun tramp--test-supports-set-file-modes-p ()
7647 "Return whether the method under test supports setting file modes." 7661 "Return whether the method under test supports setting file modes."
diff --git a/test/lisp/progmodes/eglot-tests.el b/test/lisp/progmodes/eglot-tests.el
index 7267754dc7d..ffc097fee74 100644
--- a/test/lisp/progmodes/eglot-tests.el
+++ b/test/lisp/progmodes/eglot-tests.el
@@ -1017,7 +1017,8 @@ int main() {
1017 "fn main() -> i32 { return 42.2;}") 1017 "fn main() -> i32 { return 42.2;}")
1018 ("other-file.rs" . 1018 ("other-file.rs" .
1019 "fn foo() -> () { let hi=3; }")))) 1019 "fn foo() -> () { let hi=3; }"))))
1020 (let ((eglot-server-programs '((rust-mode . ("rust-analyzer"))))) 1020 (let ((eglot-server-programs '((rust-mode . ("rust-analyzer"))))
1021 (project-vc-non-essential-cache-timeout 0))
1021 ;; Open other-file.rs, and see diagnostics arrive for main.rs, 1022 ;; Open other-file.rs, and see diagnostics arrive for main.rs,
1022 ;; which we didn't open. 1023 ;; which we didn't open.
1023 (with-current-buffer (eglot--find-file-noselect "project/other-file.rs") 1024 (with-current-buffer (eglot--find-file-noselect "project/other-file.rs")
diff --git a/test/lisp/vc/ediff-mult-tests.el b/test/lisp/vc/ediff-mult-tests.el
index e3514f77d2f..7887ae086fe 100644
--- a/test/lisp/vc/ediff-mult-tests.el
+++ b/test/lisp/vc/ediff-mult-tests.el
@@ -20,13 +20,12 @@
20;;; Code: 20;;; Code:
21 21
22(require 'ert) 22(require 'ert)
23(require 'ert-x)
23(require 'ediff-mult) 24(require 'ediff-mult)
24 25
25(ert-deftest ediff-test-bug3348 () 26(ert-deftest ediff-test-bug3348 ()
26 "After saving `ediff-meta-diff-buffer' to a file, we should not reuse it." 27 "After saving `ediff-meta-diff-buffer' to a file, we should not reuse it."
27 (let ((test-dir 28 (ert-with-temp-directory test-dir
28 (expand-file-name "bug-3348-testdir" temporary-file-directory)))
29 (make-directory test-dir t)
30 (cd test-dir) 29 (cd test-dir)
31 30
32 (make-directory "dir-a" t) 31 (make-directory "dir-a" t)
diff --git a/test/lisp/vc/vc-git-tests.el b/test/lisp/vc/vc-git-tests.el
index fe55cc75d6f..9721cc4d4ff 100644
--- a/test/lisp/vc/vc-git-tests.el
+++ b/test/lisp/vc/vc-git-tests.el
@@ -194,4 +194,39 @@ is absent."
194 ("Tracking" . ,main-branch) 194 ("Tracking" . ,main-branch)
195 ("Remote" . "none (tracking local branch)"))))))))) 195 ("Remote" . "none (tracking local branch)")))))))))
196 196
197(ert-deftest vc-git-test-branch-remotes ()
198 "Test behavior of `vc-git--branch-remotes'."
199 (skip-unless (executable-find vc-git-program))
200 (vc-git-test--with-repo repo
201 (let ((main-branch (vc-git-test--start-branch)))
202 (should (null (vc-git--branch-remotes)))
203 (vc-git--out-ok "config"
204 (format "branch.%s.remote" main-branch)
205 "origin")
206 (should (null (vc-git--branch-remotes)))
207 (vc-git--out-ok "config"
208 (format "branch.%s.merge" main-branch)
209 main-branch)
210 (let ((alist (vc-git--branch-remotes)))
211 (should (assq 'upstream alist))
212 (should (null (assq 'push alist))))
213 (vc-git--out-ok "config"
214 (format "branch.%s.pushRemote" main-branch)
215 "fork")
216 (let ((alist (vc-git--branch-remotes)))
217 (should (assq 'upstream alist))
218 (should (equal (cdr (assq 'push alist))
219 (concat "fork/" main-branch))))
220 (vc-git--out-ok "config" "--unset"
221 (format "branch.%s.pushRemote" main-branch))
222 (vc-git--out-ok "config" "remote.pushDefault" "fork")
223 (let ((alist (vc-git--branch-remotes)))
224 (should (assq 'upstream alist))
225 (should (equal (cdr (assq 'push alist))
226 (concat "fork/" main-branch))))
227 (vc-git--out-ok "config" "remote.pushDefault" "origin")
228 (let ((alist (vc-git--branch-remotes)))
229 (should (assq 'upstream alist))
230 (should (null (assq 'push alist)))))))
231
197;;; vc-git-tests.el ends here 232;;; vc-git-tests.el ends here
diff --git a/test/src/buffer-tests.el b/test/src/buffer-tests.el
index 5f534ed513a..f4654e90bce 100644
--- a/test/src/buffer-tests.el
+++ b/test/src/buffer-tests.el
@@ -1060,7 +1060,9 @@ should evaporate overlays in both."
1060 (should-length 2 (overlays-in 1 (point-max))) 1060 (should-length 2 (overlays-in 1 (point-max)))
1061 (should-length 1 (overlays-in (point-max) (point-max))) 1061 (should-length 1 (overlays-in (point-max) (point-max)))
1062 (narrow-to-region 1 50) 1062 (narrow-to-region 1 50)
1063 (should-length 1 (overlays-in 1 (point-max))) 1063 ;; We only count empty overlays in narrowed buffers excluding the
1064 ;; real EOB when the region is confined to `point-max'.
1065 (should-length 0 (overlays-in 1 (point-max)))
1064 (should-length 1 (overlays-in (point-max) (point-max)))))) 1066 (should-length 1 (overlays-in (point-max) (point-max))))))
1065 1067
1066 1068
@@ -8375,8 +8377,11 @@ dicta sunt, explicabo. "))
8375 (should (= (length (overlays-in 1 2)) 0)) 8377 (should (= (length (overlays-in 1 2)) 0))
8376 (narrow-to-region 1 2) 8378 (narrow-to-region 1 2)
8377 ;; We've now narrowed, so the zero-length overlay is at the end of 8379 ;; We've now narrowed, so the zero-length overlay is at the end of
8378 ;; the (accessible part of the) buffer. 8380 ;; the (accessible part of the) buffer, but we only count it when
8379 (should (= (length (overlays-in 1 2)) 1)) 8381 ;; the region is confined to `point-max'.
8382 (should (= (length (overlays-in 1 2)) 0))
8383 (should (= (length (overlays-in 2 2)) 1))
8384 (should (= (length (overlays-in (point-max) (point-max))) 1))
8380 (remove-overlays) 8385 (remove-overlays)
8381 (should (= (length (overlays-in (point-min) (point-max))) 0)))) 8386 (should (= (length (overlays-in (point-min) (point-max))) 0))))
8382 8387
diff --git a/test/src/data-tests.el b/test/src/data-tests.el
index 0540a99f4c3..2fc971d0214 100644
--- a/test/src/data-tests.el
+++ b/test/src/data-tests.el
@@ -951,4 +951,38 @@ comparing the subr with a much slower Lisp implementation."
951 (should-error (aset s 3 #x3fff80))) ; new char not ASCII 951 (should-error (aset s 3 #x3fff80))) ; new char not ASCII
952 ) 952 )
953 953
954(ert-deftest data-tests-per-buffer-var-predicates ()
955 (with-temp-buffer
956 ;; per buffer variable without predicate
957 (progn
958 (setq line-spacing 2.3)
959 (should (= line-spacing 2.3))
960 (setq line-spacing "2.3")
961 (should (equal line-spacing "2.3"))
962 (setq line-spacing nil)
963 (should (equal line-spacing nil)))
964 ;; per buffer variable with 'fraction predicate
965 (progn
966 (dolist (v '(nil 0.7))
967 (setq scroll-up-aggressively v)
968 (should (equal scroll-up-aggressively v)))
969 (should-error (setq scroll-up-aggressively 'abc)
970 :type 'wrong-type-argument)
971 (should-error (setq scroll-up-aggressively 2.7))
972 (should (equal scroll-up-aggressively 0.7)))
973 ;; per buffer variable with 'vertical-scroll-bar predicate
974 (progn
975 (dolist (v (get 'vertical-scroll-bar 'choice))
976 (setq vertical-scroll-bar v)
977 (should (equal vertical-scroll-bar v)))
978 (should-error (setq vertical-scroll-bar 'foo))
979 (should (equal vertical-scroll-bar 'right)))
980 ;; per buffer variable with 'overwrite-mode predicate
981 (progn
982 (dolist (v (get 'overwrite-mode 'choice))
983 (setq overwrite-mode v)
984 (should (equal overwrite-mode v)))
985 (should-error (setq overwrite-mode 'foo))
986 (should (equal overwrite-mode 'overwrite-mode-binary)))))
987
954;;; data-tests.el ends here 988;;; data-tests.el ends here