More context in Bug#80786 and: https://lists.gnu.org/archive/html/emacs-orgmode/2026-03/msg00286.html `icalendar-recur' as a type name for RRULE values was confusing and made the accessors for this type difficult to discover, because `icalendar-recur-' is also used as a prefix in icalendar-recur.el. This change renames the `icalendar-recur' type to `icalendar-rrule-value' and renames the accessor functions for these values appropriately. * lisp/calendar/icalendar-parser.el: Rename symbols as follows: (icalendar-recur): `icalendar-rrule-value' (icalendar-read-recur-rule-part): `icalendar-read-rrule-part' (icalendar-print-recur-rule-part): `icalendar-print-rrule-part' (icalendar-recur-rule-part): `icalendar-rrule-part' (icalendar-read-recur): `icalendar-read-rrule-value' (icalendar-print-recur): `icalendar-print-rrule-value' (icalendar--recur-value-types): `icalendar--rrule-value-types' (icalendar-recur-value-p): `icalendar-rrule-value-p' (icalendar-recur-freq): `icalendar-rrule-freq' (icalendar-recur-interval-size): `icalendar-rrule-interval-size' (icalendar-recur-until): `icalendar-rrule-until' (icalendar-recur-count): `icalendar-rrule-count' (icalendar-recur-weekstart): `icalendar-rrule-weekstart' (icalendar-recur-by*): `icalendar-rrule-by*'. (icalendar-rrule): (icalendar-index-insert): (icalendar-index-get): Update references. * lisp/calendar/icalendar-recur.el (icalendar-recur-find-interval): (icalendar-recur-nth-interval): (icalendar-recur-next-interval): (icalendar-recur-previous-interval): (icalendar-recur-refine-from-clauses): (icalendar-recur-recurrences-in-interval): (icalendar-recur-recurrences-in-window): (icalendar-recur-recurrences-to-count): (icalendar-recur-tz-observance-on): Update references. * lisp/calendar/diary-icalendar.el: Update references. * lisp/calendar/icalendar-shortdoc.el (icalendar): Update shortdoc examples. * lisp/gnus/gnus-icalendar.el: Update references. * test/lisp/calendar/diary-icalendar-tests.el: * test/lisp/calendar/icalendar-parser-tests.el: * test/lisp/calendar/icalendar-recur-tests.el: Update references in tests.
2908 lines
129 KiB
EmacsLisp
2908 lines
129 KiB
EmacsLisp
;;; icalendar-recur-tests.el --- Tests for icalendar-recur -*- lexical-binding: t; -*-
|
|
;; Copyright (C) 2025 Free Software Foundation, Inc.
|
|
|
|
;; This file is part of GNU Emacs.
|
|
|
|
;; GNU Emacs is free software: you can redistribute it and/or modify
|
|
;; it under the terms of the GNU General Public License as published by
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
;; (at your option) any later version.
|
|
|
|
;; GNU Emacs is distributed in the hope that it will be useful,
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
;; GNU General Public License for more details.
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
;;; Code:
|
|
|
|
(eval-when-compile (require 'cl-lib))
|
|
(require 'ert)
|
|
(eval-when-compile (require 'icalendar-macs))
|
|
(require 'icalendar-recur)
|
|
(require 'icalendar-utils)
|
|
(require 'icalendar-parser)
|
|
(require 'icalendar-ast)
|
|
|
|
;; Some constants for tests that use time zones:
|
|
(defconst ict:tz-eastern
|
|
(ical:parse-from-string 'ical:vtimezone
|
|
"BEGIN:VTIMEZONE
|
|
TZID:America/New_York
|
|
LAST-MODIFIED:20050809T050000Z
|
|
BEGIN:DAYLIGHT
|
|
DTSTART:19670430T020000
|
|
RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=-1SU;UNTIL=19730429T070000Z
|
|
TZOFFSETFROM:-0500
|
|
TZOFFSETTO:-0400
|
|
TZNAME:EDT
|
|
END:DAYLIGHT
|
|
BEGIN:STANDARD
|
|
DTSTART:19671029T020000
|
|
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=20061029T060000Z
|
|
TZOFFSETFROM:-0400
|
|
TZOFFSETTO:-0500
|
|
TZNAME:EST
|
|
END:STANDARD
|
|
BEGIN:DAYLIGHT
|
|
DTSTART:19740106T020000
|
|
RDATE:19750223T020000
|
|
TZOFFSETFROM:-0500
|
|
TZOFFSETTO:-0400
|
|
TZNAME:EDT
|
|
END:DAYLIGHT
|
|
BEGIN:DAYLIGHT
|
|
DTSTART:19760425T020000
|
|
RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=-1SU;UNTIL=19860427T070000Z
|
|
TZOFFSETFROM:-0500
|
|
TZOFFSETTO:-0400
|
|
TZNAME:EDT
|
|
END:DAYLIGHT
|
|
BEGIN:DAYLIGHT
|
|
DTSTART:19870405T020000
|
|
RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU;UNTIL=20060402T070000Z
|
|
TZOFFSETFROM:-0500
|
|
TZOFFSETTO:-0400
|
|
TZNAME:EDT
|
|
END:DAYLIGHT
|
|
BEGIN:DAYLIGHT
|
|
DTSTART:20070311T020000
|
|
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
|
|
TZOFFSETFROM:-0500
|
|
TZOFFSETTO:-0400
|
|
TZNAME:EDT
|
|
END:DAYLIGHT
|
|
BEGIN:STANDARD
|
|
DTSTART:20071104T020000
|
|
RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
|
|
TZOFFSETFROM:-0400
|
|
TZOFFSETTO:-0500
|
|
TZNAME:EST
|
|
END:STANDARD
|
|
END:VTIMEZONE
|
|
")
|
|
"`icalendar-vtimezone' representing America/New_York (Eastern) time.")
|
|
|
|
(defconst ict:est-latest
|
|
(ical:with-component ict:tz-eastern
|
|
((ical:standard :all stds))
|
|
(seq-find (lambda (obs)
|
|
(ical:date-time=
|
|
(ical:make-date-time :year 2007 :month 11 :day 4
|
|
:hour 2 :minute 0 :second 0)
|
|
(ical:with-property-of obs 'ical:dtstart nil value)))
|
|
stds))
|
|
"The observance of Eastern Standard Time which began 2007-11-04")
|
|
|
|
(defconst ict:edt-latest
|
|
(ical:with-component ict:tz-eastern
|
|
((ical:daylight :all dls))
|
|
(seq-find (lambda (obs)
|
|
(ical:date-time=
|
|
(ical:make-date-time :year 2007 :month 3 :day 11
|
|
:hour 2 :minute 0 :second 0)
|
|
(ical:with-property-of obs 'ical:dtstart nil value)))
|
|
dls))
|
|
"The observance of Eastern Daylight Time which began 2007-03-11")
|
|
|
|
(defconst ict:est -18000 ;; = -0500
|
|
"UTC offset for Eastern Standard Time")
|
|
|
|
(defconst ict:edt -14400 ;; = -0400
|
|
"UTC offset for Eastern Daylight Time")
|
|
|
|
|
|
;; Tests for basic functions:
|
|
|
|
(ert-deftest ict:recur-bysetpos-filter ()
|
|
"Test that `icr:bysetpos-filter' filters correctly by position"
|
|
(let* ((t1 (list 1 1 2024))
|
|
(t2 (list 2 1 2024))
|
|
(t3 (list 12 30 2024))
|
|
(dts (list t1 t2 t3))
|
|
(filtered (icr:bysetpos-filter (list 1 -1) dts)))
|
|
(should (member t1 filtered))
|
|
(should (member t3 filtered))
|
|
(should-not (member t2 filtered))))
|
|
|
|
(ert-deftest ict:recur-yearday-number ()
|
|
"Test that `calendar-date-from-day-of-year' finds correct dates"
|
|
(let* ((year 2025)
|
|
(daynos (list '(1 . (1 1 2025))
|
|
'(8 . (1 8 2025))
|
|
'(-1 . (12 31 2025))
|
|
'(363 . (12 29 2025)))))
|
|
(dolist (d daynos)
|
|
(let ((dayno (car d))
|
|
(date (cdr d)))
|
|
(should
|
|
(equal date (calendar-date-from-day-of-year year dayno)))))))
|
|
|
|
(ert-deftest ict:date-time-add ()
|
|
"Does `ical:date-time-add' correctly handle time zone transitions?"
|
|
;; A sum that does not use a time zone at all:
|
|
(let* ((dt (ical:make-date-time :year 2007 :month 1 :day 1
|
|
:hour 12 :minute 0 :second 0))
|
|
(delta (make-decoded-time :day 2))
|
|
(expected (ical:date-time-variant dt :day 3)))
|
|
(should (equal expected (ical:date-time-add dt delta))))
|
|
|
|
;; A sum that does not cross an observance boundary:
|
|
(let* ((dt (ical:make-date-time :year 2007 :month 2 :day 1
|
|
:hour 12 :minute 0 :second 0
|
|
:zone ict:est :dst nil))
|
|
(delta (make-decoded-time :day 2))
|
|
(expected (ical:date-time-variant dt :day 3 :tz 'preserve)))
|
|
(should (equal expected (ical:date-time-add dt delta ict:tz-eastern))))
|
|
|
|
;; A sum that crosses the Std->DST boundary and should preserve clock time:
|
|
(let* ((dt (ical:make-date-time :year 2007 :month 3 :day 10
|
|
:hour 12 :minute 0 :second 0
|
|
:zone ict:est :dst nil))
|
|
(delta (make-decoded-time :day 2))
|
|
(expected (ical:date-time-variant dt :day 12 :zone ict:edt :dst t)))
|
|
(should (equal expected (ical:date-time-add dt delta ict:tz-eastern))))
|
|
|
|
;; A sum that crosses the Std->DST boundary and should be exactly 48 hours later:
|
|
(let* ((dt (ical:make-date-time :year 2007 :month 3 :day 10
|
|
:hour 12 :minute 0 :second 0
|
|
:zone ict:est :dst nil))
|
|
(delta (make-decoded-time :hour 48))
|
|
(expected (ical:date-time-variant dt :day 12 :hour 13
|
|
:zone ict:edt :dst t)))
|
|
(should (equal expected (ical:date-time-add dt delta ict:tz-eastern))))
|
|
|
|
;; A sum that crosses the DST->Std boundary and should preserve clock time:
|
|
(let* ((dt (ical:make-date-time :year 2007 :month 11 :day 3
|
|
:hour 12 :minute 0 :second 0
|
|
:zone ict:edt :dst t))
|
|
(delta (make-decoded-time :day 2))
|
|
(expected (ical:date-time-variant dt :day 5 :zone ict:est :dst nil)))
|
|
(should (equal expected (ical:date-time-add dt delta ict:tz-eastern))))
|
|
|
|
;; A sum that crosses the DST->Std boundary and should be exactly 48 hours later:
|
|
(let* ((dt (ical:make-date-time :year 2007 :month 11 :day 3
|
|
:hour 12 :minute 0 :second 0
|
|
:zone ict:edt :dst t))
|
|
(delta (make-decoded-time :hour 48))
|
|
(expected (ical:date-time-variant dt :day 5 :hour 11
|
|
:zone ict:est :dst nil)))
|
|
(should (equal expected (ical:date-time-add dt delta ict:tz-eastern))))
|
|
|
|
;; A sum that lands exactly on the Std->DST boundary and should result
|
|
;; in a clock time one hour later:
|
|
(let* ((dt (ical:make-date-time :year 2007 :month 3 :day 10
|
|
:hour 2 :minute 0 :second 0
|
|
:zone ict:est :dst nil))
|
|
(delta (make-decoded-time :hour 24))
|
|
(expected (ical:date-time-variant dt :day 11 :hour 3
|
|
:zone ict:edt :dst t)))
|
|
(should (equal expected (ical:date-time-add dt delta ict:tz-eastern))))
|
|
|
|
;; A sum that lands exactly on the DST->Std boundary and should result
|
|
;; in a clock time one hour earlier:
|
|
(let* ((dt (ical:make-date-time :year 2007 :month 11 :day 3
|
|
:hour 2 :minute 0 :second 0
|
|
:zone ict:edt :dst t))
|
|
(delta (make-decoded-time :hour 24))
|
|
(expected (ical:date-time-variant dt :day 4 :hour 1
|
|
:zone ict:est :dst nil)))
|
|
(should (equal expected (ical:date-time-add dt delta ict:tz-eastern)))))
|
|
|
|
(ert-deftest ict:recur-nonexistent-date-time-p ()
|
|
"Does `icr:nonexistent-date-time-p' correctly identify nonexistent times?"
|
|
(let* ((dst-onset (ical:make-date-time :year 2025 :month 3 :day 9
|
|
:hour 2 :minute 0 :second 0
|
|
:zone ict:est :dst nil))
|
|
;; 2:30 AM falls into the gap when shifting from 2AM EST to 3AM EDT:
|
|
(nonexistent1 (ical:make-date-time :year 2025 :month 3 :day 9
|
|
:hour 2 :minute 30 :second 0
|
|
:zone ict:est :dst nil))
|
|
(nonexistent2 (ical:date-time-variant nonexistent1
|
|
:zone ict:edt :dst t))
|
|
(std-onset (ical:make-date-time :year 2025 :month 11 :day 2
|
|
:hour 2 :minute 0 :second 0
|
|
:zone ict:edt :dst t))
|
|
;; 1:30AM around the shift back to EST exists twice (once in
|
|
;; EDT, once in EST) and should not be nonexistent:
|
|
(existent1 (ical:make-date-time :year 2025 :month 11 :day 2
|
|
:hour 1 :minute 30 :second 0
|
|
:zone ict:edt :dst t))
|
|
(existent2 (ical:make-date-time :year 2025 :month 11 :day 2
|
|
:hour 1 :minute 30 :second 0
|
|
:zone ict:est :dst nil)))
|
|
(should (icr:nonexistent-date-time-p nonexistent1 dst-onset ict:edt-latest))
|
|
(should (icr:nonexistent-date-time-p nonexistent2 dst-onset ict:edt-latest))
|
|
(should-not
|
|
(icr:nonexistent-date-time-p existent1 std-onset ict:est-latest))
|
|
(should-not
|
|
(icr:nonexistent-date-time-p existent2 std-onset ict:est-latest))))
|
|
|
|
(ert-deftest ict:recur-date-time-occurs-twice-p ()
|
|
"Does `icr:date-time-occurs-twice-p' correctly identify times that occur twice?"
|
|
(let* ((std-onset (ical:make-date-time :year 2025 :month 11 :day 2
|
|
:hour 2 :minute 0 :second 0
|
|
:zone ict:edt :dst t))
|
|
;; 1:00, 1:30 AM occur twice when shifting from 2AM EDT to 1AM EST:
|
|
(twice1 (ical:make-date-time :year 2025 :month 11 :day 2
|
|
:hour 1 :minute 0 :second 0))
|
|
(twice2 (ical:make-date-time :year 2025 :month 11 :day 2
|
|
:hour 1 :minute 30 :second 0))
|
|
;; 12:59 AM, 2AM should not occur twice:
|
|
(once1 (ical:make-date-time :year 2025 :month 11 :day 2
|
|
:hour 0 :minute 59 :second 0
|
|
:zone ict:edt :dst t))
|
|
(once2 (ical:make-date-time :year 2025 :month 11 :day 2
|
|
:hour 2 :minute 0 :second 0
|
|
:zone ict:est :dst nil)))
|
|
(should (icr:date-time-occurs-twice-p twice1 std-onset ict:est-latest))
|
|
(should (icr:date-time-occurs-twice-p twice2 std-onset ict:est-latest))
|
|
(should-not
|
|
(icr:date-time-occurs-twice-p once1 std-onset ict:est-latest))
|
|
(should-not
|
|
(icr:date-time-occurs-twice-p once2 std-onset ict:est-latest))))
|
|
|
|
(ert-deftest ict:recur-find-secondly-interval ()
|
|
"Does `icr:find-secondly-interval' find correct intervals?"
|
|
(let* ((dtstart (ical:make-date-time :year 2025 :month 1 :day 1
|
|
:hour 0 :minute 0 :second 0
|
|
;; Use UTC for the tests with no
|
|
;; time zone, so that the results
|
|
;; don't depend on system's local time
|
|
:zone 0))
|
|
(dtstart/tz (ical:date-time-variant dtstart :zone ict:est :dst nil)))
|
|
|
|
;; Year numbers are monotonically increasing in the following test cases,
|
|
;; to make it easy to tell which of them fails.
|
|
|
|
;; No timezone, just clock time, around a target that doesn't fall on
|
|
;; an interval boundary:
|
|
(let* ((target (ical:date-time-variant dtstart :year 2026 :second 5 :zone 0))
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:date-time-variant target :second 0 :tz 'preserve)
|
|
(ical:date-time-variant target :second 1 :tz 'preserve)
|
|
(ical:date-time-variant target :second 10 :tz 'preserve))))
|
|
(should
|
|
(equal expected-int
|
|
(icr:find-secondly-interval target dtstart 10))))
|
|
|
|
;; No timezone, just clock time, around a target that does fall on
|
|
;; an interval boundary:
|
|
(let* ((target (ical:date-time-variant dtstart :year 2027 :second 10 :zone 0))
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:date-time-variant target :second 10 :tz 'preserve)
|
|
(ical:date-time-variant target :second 11 :tz 'preserve)
|
|
(ical:date-time-variant target :second 20 :tz 'preserve))))
|
|
(should
|
|
(equal expected-int
|
|
(icr:find-secondly-interval target dtstart 10))))
|
|
|
|
;; With timezone, around a target that falls on an interval
|
|
;; boundary, in the same observance:
|
|
(let* ((target (ical:date-time-variant dtstart/tz
|
|
:year 2028 :month 2 :second 20
|
|
:zone ict:est :dst nil))
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:date-time-variant target :second 20 :tz 'preserve)
|
|
(ical:date-time-variant target :second 21 :tz 'preserve)
|
|
(ical:date-time-variant target :second 30 :tz 'preserve))))
|
|
(should
|
|
(equal expected-int
|
|
(icr:find-secondly-interval target dtstart/tz 10
|
|
ict:tz-eastern))))
|
|
|
|
;; With timezone, around a target that does not fall on an interval
|
|
;; boundary, and after the time zone observance shift:
|
|
(let* ((target (ical:date-time-variant dtstart/tz
|
|
:year 2029 :month 5 :second 30
|
|
:zone ict:edt :dst t))
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:date-time-variant target :second 30 :tz 'preserve)
|
|
(ical:date-time-variant target :second 31 :tz 'preserve)
|
|
(ical:date-time-variant target :second 40 :tz 'preserve))))
|
|
(should
|
|
(equal expected-int
|
|
(icr:find-secondly-interval target dtstart/tz 10 ict:tz-eastern))))
|
|
|
|
;; With timezone, around a target that falls into the gap in local
|
|
;; times and thus does not exist as a local time. In this case, what
|
|
;; is supposed to happen is that the clock time value in the [observance]
|
|
;; recurrences "is interpreted using the UTC offset before the gap
|
|
;; in local times." So we should get the same absolute times back,
|
|
;; but re-decoded into the new observance, i.e., one hour later.
|
|
(let* ((target (ical:date-time-variant dtstart/tz
|
|
:year 2030 :month 3 :day 10
|
|
:hour 2 :minute 30 :second 0
|
|
:zone ict:est :dst nil))
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:date-time-variant target :hour 3 :second 0
|
|
:zone ict:edt :dst t)
|
|
(ical:date-time-variant target :hour 3 :second 1
|
|
:zone ict:edt :dst t)
|
|
(ical:date-time-variant target
|
|
:hour 3 :second 10
|
|
:zone ict:edt :dst t))))
|
|
(should
|
|
(equal expected-int
|
|
(icr:find-secondly-interval target dtstart/tz 10 ict:tz-eastern))))
|
|
|
|
;; With timezone, with a "pathological" interval size of 59 seconds.
|
|
;; There should be no problem with this case, because the interval
|
|
;; bounds calculation is done in absolute time, but it's annoying to
|
|
;; calculate the expected interval by hand:
|
|
(let* ((target (ical:date-time-variant dtstart/tz
|
|
:year 2031 :month 4 :day 15
|
|
:hour 12 :minute 0 :second 0
|
|
:zone ict:edt :dst t))
|
|
(intsize 59)
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:date-time-variant target :hour 11 :minute 59 :second 16
|
|
:tz 'preserve)
|
|
(ical:date-time-variant target :hour 11 :minute 59 :second 17
|
|
:tz 'preserve)
|
|
(ical:date-time-variant target :hour 12 :minute 0 :second 15
|
|
:tz 'preserve))))
|
|
(should
|
|
(equal expected-int
|
|
(icr:find-secondly-interval target dtstart/tz intsize
|
|
ict:tz-eastern))))))
|
|
|
|
(ert-deftest ict:recur-find-minutely-interval ()
|
|
"Does `icr:find-minutely-interval' find correct intervals?"
|
|
(let* ((dtstart (ical:make-date-time :year 2025 :month 1 :day 1
|
|
:hour 0 :minute 0
|
|
;; make sure intervals are
|
|
;; bounded on whole minutes:
|
|
:second 23))
|
|
(dtstart/tz (ical:date-time-variant dtstart :zone ict:est :dst nil)))
|
|
|
|
;; Year numbers are monotonically increasing in the following test cases,
|
|
;; to make it easy to tell which of them fails.
|
|
|
|
;; No timezone, just a fixed offset, around a target that doesn't fall on
|
|
;; an interval boundary:
|
|
(let* ((target (ical:date-time-variant dtstart :year 2026 :minute 5))
|
|
(intsize 10)
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:date-time-variant target :minute 0 :second 0)
|
|
(ical:date-time-variant target :minute 1 :second 0)
|
|
(ical:date-time-variant target :minute 10 :second 0))))
|
|
(should
|
|
(equal expected-int
|
|
(icr:find-minutely-interval target dtstart intsize))))
|
|
|
|
;; No timezone, just clock time, around a target that does fall on
|
|
;; an interval boundary:
|
|
(let* ((target (ical:date-time-variant dtstart :year 2027 :minute 10))
|
|
(intsize 10)
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:date-time-variant target :minute 10 :second 0)
|
|
(ical:date-time-variant target :minute 11 :second 0)
|
|
(ical:date-time-variant target :minute 20 :second 0))))
|
|
(should
|
|
(equal expected-int
|
|
(icr:find-minutely-interval target dtstart intsize))))
|
|
|
|
;; With timezone, around a target that falls on an interval
|
|
;; boundary, in the same observance:
|
|
(let* ((target (ical:date-time-variant dtstart/tz
|
|
:year 2028 :month 2 :minute 20
|
|
:zone ict:est :dst nil))
|
|
(intsize 10)
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:date-time-variant target :minute 20 :second 0
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant target :minute 21 :second 0
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant target :minute 30 :second 0
|
|
:zone ict:est :dst nil))))
|
|
(should
|
|
(equal expected-int
|
|
(icr:find-minutely-interval target dtstart/tz intsize
|
|
ict:tz-eastern))))
|
|
|
|
;; With timezone, around a target that does not fall on an interval
|
|
;; boundary, and after the time zone observance shift:
|
|
(let* ((target (ical:date-time-variant dtstart/tz
|
|
:year 2029 :month 5 :minute 30
|
|
:zone ict:edt :dst t))
|
|
(intsize 10)
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:date-time-variant target :minute 30 :second 0
|
|
:zone ict:edt :dst t)
|
|
(ical:date-time-variant target :minute 31 :second 0
|
|
:zone ict:edt :dst t)
|
|
(ical:date-time-variant target :minute 40 :second 0
|
|
:zone ict:edt :dst t))))
|
|
(should
|
|
(equal expected-int
|
|
(icr:find-minutely-interval target dtstart/tz intsize
|
|
ict:tz-eastern))))
|
|
|
|
;; With timezone, around a target that falls into the gap in local
|
|
;; times and thus does not exist as a local time. In this case, what
|
|
;; is supposed to happen is that the clock time value in the [observance]
|
|
;; recurrences "is interpreted using the UTC offset before the gap
|
|
;; in local times." So we should get the same absolute times back,
|
|
;; but re-decoded into the new observance, i.e., one hour later.
|
|
(let* ((target (ical:date-time-variant dtstart/tz
|
|
:year 2030 :month 3 :day 10
|
|
:hour 2 :minute 30 :second 0
|
|
:zone ict:est :dst nil))
|
|
(intsize 10)
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:date-time-variant target :hour 3 :minute 30 :second 0
|
|
:zone ict:edt :dst t)
|
|
(ical:date-time-variant target :hour 3 :minute 31 :second 0
|
|
:zone ict:edt :dst t)
|
|
(ical:date-time-variant target
|
|
:hour 3 :minute 40 :second 0
|
|
:zone ict:edt :dst t))))
|
|
(should
|
|
(equal expected-int
|
|
(icr:find-minutely-interval target dtstart/tz intsize
|
|
ict:tz-eastern))))))
|
|
|
|
(ert-deftest ict:recur-find-hourly-interval ()
|
|
"Does `icr:find-hourly-interval' find correct intervals?"
|
|
(let* ((dtstart (ical:make-date-time :year 2025 :month 1 :day 1
|
|
:hour 0
|
|
;; make sure intervals are bounded on
|
|
;; whole hours:
|
|
:minute 11 :second 23))
|
|
(dtstart/tz (ical:date-time-variant dtstart :zone ict:est :dst nil)))
|
|
|
|
;; Year numbers are monotonically increasing in the following test cases,
|
|
;; to make it easy to tell which of them fails.
|
|
;; No timezone, just clock time, around a target that doesn't fall on
|
|
;; an interval boundary:
|
|
(let* ((target (ical:date-time-variant dtstart :year 2026 :hour 5))
|
|
(intsize 10)
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:date-time-variant target :hour 0 :minute 0 :second 0)
|
|
(ical:date-time-variant target :hour 1 :minute 0 :second 0)
|
|
(ical:date-time-variant target :hour 10 :minute 0 :second 0))))
|
|
(should
|
|
(equal expected-int
|
|
(icr:find-hourly-interval target dtstart intsize))))
|
|
|
|
;; No timezone, just clock time, around a target that does fall on
|
|
;; an interval boundary:
|
|
(let* ((target (ical:date-time-variant dtstart :year 2027 :hour 10))
|
|
(intsize 10)
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:date-time-variant target :hour 10 :minute 0 :second 0)
|
|
(ical:date-time-variant target :hour 11 :minute 0 :second 0)
|
|
(ical:date-time-variant target :hour 20 :minute 0 :second 0))))
|
|
(should
|
|
(equal expected-int
|
|
(icr:find-hourly-interval target dtstart intsize))))
|
|
|
|
;; With timezone, around a target that falls on an interval
|
|
;; boundary, in the same observance:
|
|
(let* ((target (ical:date-time-variant dtstart/tz
|
|
:year 2028 :month 2 :hour 10
|
|
:zone ict:est :dst nil))
|
|
(intsize 2)
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:date-time-variant target :hour 10 :minute 0 :second 0
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant target :hour 11 :minute 0 :second 0
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant target :hour 12 :minute 0 :second 0
|
|
:zone ict:est :dst nil))))
|
|
(should
|
|
(equal expected-int
|
|
(icr:find-hourly-interval target dtstart/tz intsize
|
|
ict:tz-eastern))))
|
|
|
|
;; With time zone, around a target that does not fall on an interval
|
|
;; boundary, and after the time zone observance shift. Note that
|
|
;; because of our decision to calculate with absolute times in
|
|
;; SECONDLY/MINUTELY/HOURLY rules (see `icr:find-secondly-recurrence-rule')
|
|
;; the interval clock times shift an hour here:
|
|
(let* ((target (ical:date-time-variant dtstart/tz
|
|
:year 2029 :month 5 :hour 12
|
|
:zone ict:edt :dst t))
|
|
(intsize 2)
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:date-time-variant target :hour 11 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
(ical:date-time-variant target :hour 12 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
(ical:date-time-variant target :hour 13 :minute 0 :second 0
|
|
:zone ict:edt :dst t))))
|
|
(should
|
|
(equal expected-int
|
|
(icr:find-hourly-interval target dtstart/tz intsize
|
|
ict:tz-eastern))))
|
|
|
|
;; With timezone, around a target that falls into the gap in local
|
|
;; times and thus does not exist as a local time. In this case, what
|
|
;; is supposed to happen is that the clock time value in the [observance]
|
|
;; recurrences "is interpreted using the UTC offset before the gap
|
|
;; in local times." So we should get the same absolute times back,
|
|
;; but re-decoded into the new observance, i.e., one hour later.
|
|
(let* ((target (ical:make-date-time :year 2030 :month 3 :day 10
|
|
:hour 2 :minute 30 :second 30
|
|
:zone ict:est :dst nil))
|
|
(intsize 2)
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:date-time-variant target :hour 3 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
(ical:date-time-variant target :hour 4 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
(ical:date-time-variant target :hour 5 :minute 0 :second 0
|
|
:zone ict:edt :dst t))))
|
|
(should
|
|
(equal expected-int
|
|
(icr:find-hourly-interval target dtstart/tz intsize
|
|
ict:tz-eastern))))))
|
|
|
|
(ert-deftest ict:recur-find-daily-interval-w/date ()
|
|
"Does `icr:find-daily-interval' find correct date intervals?"
|
|
(let* ((dtstart (list 1 8 2025)))
|
|
;; Since all the results should be the same after the initial
|
|
;; calculation of the absolute dates DTSTART and TARGET, we just
|
|
;; test one simple case here and test with date-times more
|
|
;; thoroughly below.
|
|
|
|
;; A target that doesn't fall on an interval boundary:
|
|
(let* ((target (list 1 9 2026))
|
|
(intsize 7)
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:make-date-time :year 2026 :month 1 :day 7
|
|
:hour 0 :minute 0 :second 0)
|
|
(ical:make-date-time :year 2026 :month 1 :day 8
|
|
:hour 0 :minute 0 :second 0)
|
|
(ical:make-date-time :year 2026 :month 1 :day 14
|
|
:hour 0 :minute 0 :second 0))))
|
|
(should (equal expected-int
|
|
(icr:find-daily-interval target dtstart intsize))))))
|
|
|
|
(ert-deftest ict:recur-find-daily-interval-w/date-time ()
|
|
"Does `icr:find-daily-interval' find correct date-time intervals?"
|
|
(let* ((dtstart (ical:make-date-time :year 2025 :month 1 :day 8 ; a Wednesday
|
|
;; make sure intervals are bounded on
|
|
;; whole days:
|
|
:hour 7 :minute 11 :second 23))
|
|
(dtstart/tz (ical:date-time-variant dtstart :zone ict:est :dst nil)))
|
|
|
|
;; Year numbers are monotonically increasing in the following test cases,
|
|
;; to make it easy to tell which of them fails.
|
|
|
|
;; No timezone, just clock time, around a target that doesn't fall on
|
|
;; an interval boundary:
|
|
(let* ((target (ical:date-time-variant dtstart
|
|
:year 2026 :month 1 :day 9))
|
|
(intsize 7)
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:date-time-variant target :day 7 :hour 0 :minute 0 :second 0)
|
|
(ical:date-time-variant target :day 8 :hour 0 :minute 0 :second 0)
|
|
(ical:date-time-variant target :day 14
|
|
:hour 0 :minute 0 :second 0))))
|
|
(should
|
|
(equal expected-int
|
|
(icr:find-daily-interval target dtstart intsize))))
|
|
|
|
;; No timezone, just clock time, around a target that does fall on
|
|
;; an interval boundary:
|
|
(let* ((target (ical:date-time-variant dtstart :year 2027 :month 1 :day 6))
|
|
(intsize 7)
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:date-time-variant target :day 6 :hour 0 :minute 0 :second 0)
|
|
(ical:date-time-variant target :day 7 :hour 0 :minute 0 :second 0)
|
|
(ical:date-time-variant target :day 13 :hour 0 :minute 0 :second 0))))
|
|
(should
|
|
(equal expected-int
|
|
(icr:find-daily-interval target dtstart intsize))))
|
|
|
|
;; With timezone, around a target that falls on an interval
|
|
;; boundary, in the same observance:
|
|
(let* ((target (ical:date-time-variant dtstart/tz :year 2028 :month 2 :day 2
|
|
:zone ict:est :dst nil))
|
|
(intsize 7)
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:date-time-variant target :day 2 :hour 0 :minute 0 :second 0
|
|
:tz 'preserve)
|
|
(ical:date-time-variant target :day 3 :hour 0 :minute 0 :second 0
|
|
:tz 'preserve)
|
|
(ical:date-time-variant target :day 9 :hour 0 :minute 0 :second 0
|
|
:tz 'preserve))))
|
|
(should
|
|
(equal expected-int
|
|
(icr:find-daily-interval target dtstart/tz intsize ict:tz-eastern))))
|
|
|
|
;; With time zone, around a target that does not fall on an interval
|
|
;; boundary, and after the time zone observance shift.
|
|
(let* ((target (ical:date-time-variant dtstart/tz
|
|
:year 2029 :month 5 :day 28
|
|
:zone ict:edt :dst t))
|
|
(intsize 7)
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:date-time-variant target :day 23 :hour 0 :minute 0 :second 0
|
|
:tz 'preserve)
|
|
(ical:date-time-variant target :day 24 :hour 0 :minute 0 :second 0
|
|
:tz 'preserve)
|
|
(ical:date-time-variant target :day 30 :hour 0 :minute 0 :second 0
|
|
:tz 'preserve))))
|
|
(should
|
|
(equal expected-int
|
|
(icr:find-daily-interval target dtstart/tz intsize
|
|
ict:tz-eastern))))))
|
|
|
|
(ert-deftest ict:recur-find-weekly-interval-w/date ()
|
|
"Does `icr:find-weekly-interval' find correct date intervals?"
|
|
(let* ((dtstart '(1 8 2025)))
|
|
;; Since all the results should be the same after the initial
|
|
;; calculation of the absolute dates DTSTART and TARGET, we just
|
|
;; test one simple case here and test with date-times more
|
|
;; thoroughly below.
|
|
|
|
;; A target that doesn't fall on an interval boundary:
|
|
(let* ((target '(1 9 2026))
|
|
(intsize 2)
|
|
(expected-int-mon
|
|
(icr:make-interval
|
|
(ical:make-date-time :year 2026 :month 1 :day 5
|
|
:hour 0 :minute 0 :second 0)
|
|
(ical:make-date-time :year 2026 :month 1 :day 12
|
|
:hour 0 :minute 0 :second 0)
|
|
(ical:make-date-time :year 2026 :month 1 :day 19
|
|
:hour 0 :minute 0 :second 0))))
|
|
(should (equal expected-int-mon
|
|
(icr:find-weekly-interval target dtstart intsize))))))
|
|
|
|
(ert-deftest ict:recur-find-weekly-interval-w/date-time ()
|
|
"Does `icr:find-weekly-interval' find correct date-time intervals?"
|
|
(let* ((dtstart (ical:make-date-time :year 2025 :month 1 :day 8 ; a Wednesday
|
|
;; make sure intervals are bounded on
|
|
;; whole days:
|
|
:hour 7 :minute 11 :second 23)))
|
|
|
|
;; Year numbers are monotonically increasing in the following test cases,
|
|
;; to make it easy to tell which of them fails.
|
|
|
|
;; No timezone, just clock time, around a target that doesn't fall on
|
|
;; an interval boundary:
|
|
(let* ((target (ical:date-time-variant dtstart :year 2026 :month 1 :day 9))
|
|
(intsize 2)
|
|
(weds 3)
|
|
;; expected interval for Monday (default) week start:
|
|
(expected-int-mon
|
|
(icr:make-interval
|
|
(ical:date-time-variant target :day 5 :hour 0 :minute 0 :second 0)
|
|
(ical:date-time-variant target :day 12 :hour 0 :minute 0 :second 0)
|
|
(ical:date-time-variant target :day 19 :hour 0 :minute 0 :second 0)))
|
|
;; expected interval for Wednesday week start:
|
|
(expected-int-wed
|
|
(icr:make-interval
|
|
(ical:date-time-variant target :day 7 :hour 0 :minute 0 :second 0)
|
|
(ical:date-time-variant target :day 14 :hour 0 :minute 0 :second 0)
|
|
(ical:date-time-variant target :day 21 :hour 0 :minute 0 :second 0))))
|
|
(should
|
|
(equal expected-int-mon
|
|
(icr:find-weekly-interval target dtstart intsize)))
|
|
(should
|
|
(equal expected-int-wed
|
|
(icr:find-weekly-interval target dtstart intsize weds))))
|
|
|
|
;; Around a target that does fall on an interval boundary, Monday week start:
|
|
(let* ((target (ical:date-time-variant dtstart :year 2027 :month 1 :day 4))
|
|
(intsize 3)
|
|
;; expected interval for Monday (default) week start:
|
|
(expected-int-mon
|
|
(icr:make-interval
|
|
(ical:date-time-variant target :year 2026 :month 12 :day 21
|
|
:hour 0 :minute 0 :second 0)
|
|
(ical:date-time-variant target :year 2026 :month 12 :day 28
|
|
:hour 0 :minute 0 :second 0)
|
|
(ical:date-time-variant target :day 11
|
|
:hour 0 :minute 0 :second 0))))
|
|
(should
|
|
(equal expected-int-mon
|
|
(icr:find-weekly-interval target dtstart intsize))))
|
|
|
|
;; Around a target that does fall on an interval boundary, Sunday week start:
|
|
(let* ((target (ical:date-time-variant dtstart :year 2028 :month 1 :day 2))
|
|
(intsize 3)
|
|
(sun 0)
|
|
;; expected interval for Sunday week start:
|
|
(expected-int-sun
|
|
(icr:make-interval
|
|
(ical:date-time-variant target :day 2 :hour 0 :minute 0 :second 0)
|
|
(ical:date-time-variant target :day 9 :hour 0 :minute 0 :second 0)
|
|
(ical:date-time-variant target :day 23 :hour 0 :minute 0 :second 0))))
|
|
(should
|
|
(equal expected-int-sun
|
|
(icr:find-weekly-interval target dtstart intsize sun))))))
|
|
|
|
(ert-deftest ict:recur-find-monthly-interval ()
|
|
"Does `icr:find-monthly-interval' find correct intervals?"
|
|
;; Year numbers are monotonically increasing in the following test cases,
|
|
;; to make it easy to tell which of them fails.
|
|
|
|
;; One test with dates, to make sure that works:
|
|
(let* ((dtstart '(1 8 2025))
|
|
(target '(10 9 2025))
|
|
(intsize 5)
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:make-date-time :year 2025 :month 6 :day 1
|
|
:hour 0 :minute 0 :second 0)
|
|
(ical:make-date-time :year 2025 :month 7 :day 1
|
|
:hour 0 :minute 0 :second 0)
|
|
(ical:make-date-time :year 2025 :month 11 :day 1
|
|
:hour 0 :minute 0 :second 0))))
|
|
(should (equal expected-int
|
|
(icr:find-monthly-interval target dtstart intsize))))
|
|
|
|
;; Around a target that doesn't fall on an interval boundary:
|
|
(let* ((dtstart (ical:make-date-time :year 2025 :month 1 :day 1
|
|
;; make sure intervals are bounded on
|
|
;; whole days:
|
|
:hour 7 :minute 11 :second 23))
|
|
(target (ical:date-time-variant dtstart :year 2026 :month 3 :day 9))
|
|
(intsize 2)
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:date-time-variant target :day 1 :hour 0 :minute 0 :second 0)
|
|
(ical:date-time-variant target :month 4 :day 1
|
|
:hour 0 :minute 0 :second 0)
|
|
(ical:date-time-variant target :month 5 :day 1
|
|
:hour 0 :minute 0 :second 0))))
|
|
(should
|
|
(equal expected-int
|
|
(icr:find-monthly-interval target dtstart intsize))))
|
|
|
|
;; Around a target that does fall on an interval boundary:
|
|
(let* ((dtstart (ical:make-date-time :year 2025 :month 1 :day 1
|
|
;; make sure intervals are bounded on
|
|
;; whole days:
|
|
:hour 7 :minute 11 :second 23))
|
|
(target (ical:date-time-variant dtstart :year 2027 :month 5 :day 1))
|
|
(intsize 7)
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:date-time-variant target :year 2027 :month 5 :day 1
|
|
:hour 0 :minute 0 :second 0)
|
|
(ical:date-time-variant target :year 2027 :month 6 :day 1
|
|
:hour 0 :minute 0 :second 0)
|
|
(ical:date-time-variant target :year 2027 :month 12 :day 1
|
|
:hour 0 :minute 0 :second 0))))
|
|
(should
|
|
(equal expected-int
|
|
(icr:find-monthly-interval target dtstart intsize))))
|
|
|
|
;; Around a target that does not fall on an interval boundary, where
|
|
;; start month > target month
|
|
(let* ((dtstart (ical:make-date-time :year 2028 :month 11 :day 11
|
|
:hour 11 :minute 11 :second 11))
|
|
(target (ical:date-time-variant dtstart
|
|
:year 2029 :month 4 :day 15))
|
|
(intsize 2)
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:date-time-variant target :year 2029 :month 3 :day 1
|
|
:hour 0 :minute 0 :second 0)
|
|
(ical:date-time-variant target :year 2029 :month 4 :day 1
|
|
:hour 0 :minute 0 :second 0)
|
|
(ical:date-time-variant target :year 2029 :month 5 :day 1
|
|
:hour 0 :minute 0 :second 0))))
|
|
(should
|
|
(equal expected-int
|
|
(icr:find-monthly-interval target dtstart intsize))))
|
|
|
|
;; Around a target that falls on an interval boundary, where
|
|
;; start month > target month
|
|
(let* ((dtstart (ical:make-date-time :year 2029 :month 11 :day 11
|
|
:hour 11 :minute 11 :second 11 ))
|
|
(target (ical:date-time-variant dtstart
|
|
:year 2030 :month 5 :day 1))
|
|
(intsize 2)
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:date-time-variant target :year 2030 :month 5 :day 1
|
|
:hour 0 :minute 0 :second 0)
|
|
(ical:date-time-variant target :year 2030 :month 6 :day 1
|
|
:hour 0 :minute 0 :second 0)
|
|
(ical:date-time-variant target :year 2030 :month 7 :day 1
|
|
:hour 0 :minute 0 :second 0))))
|
|
(should
|
|
(equal expected-int
|
|
(icr:find-monthly-interval target dtstart intsize))))
|
|
|
|
;; Around a target that falls on an interval boundary, where
|
|
;; start month = target month
|
|
(let* ((dtstart (ical:make-date-time :year 2031 :month 11 :day 11
|
|
:hour 11 :minute 11 :second 11 ))
|
|
(target (ical:date-time-variant dtstart :year 2032 :month 11 :day 11))
|
|
(intsize 2)
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:date-time-variant target :year 2032 :month 11 :day 1
|
|
:hour 0 :minute 0 :second 0)
|
|
(ical:date-time-variant target :year 2032 :month 12 :day 1
|
|
:hour 0 :minute 0 :second 0)
|
|
(ical:date-time-variant target :year 2033 :month 1 :day 1
|
|
:hour 0 :minute 0 :second 0))))
|
|
(should
|
|
(equal expected-int
|
|
(icr:find-monthly-interval target dtstart intsize)))))
|
|
|
|
(ert-deftest ict:recur-find-yearly-interval ()
|
|
"Does `icr:find-yearly-interval' find correct date intervals?"
|
|
;; Year numbers are monotonically increasing in the following test cases,
|
|
;; to make it easy to tell which of them fails.
|
|
|
|
;; One test with dates, to make sure that works:
|
|
(let* ((dtstart '(1 8 2025))
|
|
(target '(10 9 2025))
|
|
(intsize 2)
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:make-date-time :year 2025 :month 1 :day 1
|
|
:hour 0 :minute 0 :second 0)
|
|
(ical:make-date-time :year 2026 :month 1 :day 1
|
|
:hour 0 :minute 0 :second 0)
|
|
(ical:make-date-time :year 2027 :month 1 :day 1
|
|
:hour 0 :minute 0 :second 0))))
|
|
(should (equal expected-int
|
|
(icr:find-yearly-interval target dtstart intsize))))
|
|
|
|
;; A target not on an interval boundary:
|
|
(let* ((dtstart (ical:make-date-time :year 2026 :month 3 :day 1
|
|
:hour 1 :minute 2 :second 3))
|
|
(target (ical:make-date-time :year 2026 :month 7 :day 28
|
|
:hour 11 :minute 58 :second 0))
|
|
(intsize 3)
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:make-date-time :year 2026 :month 1 :day 1
|
|
:hour 0 :minute 0 :second 0)
|
|
(ical:make-date-time :year 2027 :month 1 :day 1
|
|
:hour 0 :minute 0 :second 0)
|
|
(ical:make-date-time :year 2029 :month 1 :day 1
|
|
:hour 0 :minute 0 :second 0))))
|
|
(should (equal expected-int
|
|
(icr:find-yearly-interval target dtstart intsize))))
|
|
|
|
;; A target on an interval boundary:
|
|
(let* ((dtstart (ical:make-date-time :year 2027 :month 3 :day 1
|
|
:hour 1 :minute 2 :second 3))
|
|
(target (ical:make-date-time :year 2028 :month 1 :day 1
|
|
:hour 0 :minute 0 :second 0))
|
|
(intsize 4)
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:make-date-time :year 2027 :month 1 :day 1
|
|
:hour 0 :minute 0 :second 0)
|
|
(ical:make-date-time :year 2028 :month 1 :day 1
|
|
:hour 0 :minute 0 :second 0)
|
|
(ical:make-date-time :year 2031 :month 1 :day 1
|
|
:hour 0 :minute 0 :second 0))))
|
|
(should (equal expected-int
|
|
(icr:find-yearly-interval target dtstart intsize))))
|
|
|
|
;; A target earlier than dtstart but in the same year;
|
|
;; it's important that this works when looking up recurrences of
|
|
;; time zone observance onsets
|
|
(let* ((dtstart (ical:make-date-time :year 2029 :month 5 :day 28
|
|
:hour 1 :minute 2 :second 3))
|
|
(target (ical:make-date-time :year 2029 :month 2 :day 14
|
|
:hour 11 :minute 58 :second 0))
|
|
(intsize 1)
|
|
(expected-int
|
|
(icr:make-interval
|
|
(ical:make-date-time :year 2029 :month 1 :day 1
|
|
:hour 0 :minute 0 :second 0)
|
|
(ical:make-date-time :year 2030 :month 1 :day 1
|
|
:hour 0 :minute 0 :second 0)
|
|
nil)))
|
|
(should (equal expected-int
|
|
(icr:find-yearly-interval target dtstart intsize)))))
|
|
|
|
;; Subintervals:
|
|
|
|
(ert-deftest ict:recur-refine-byyearday ()
|
|
"Does `icr:refine-byyearday' correctly refine by yeardays?"
|
|
(let* ((low (ical:make-date-time :year 2025 :month 1 :day 1
|
|
:hour 0 :minute 0 :second 0))
|
|
(high (ical:date/time-add low :year 1))
|
|
(interval (icr:make-interval low high high))
|
|
(yeardays (list 2 -7))
|
|
(sub1 (icr:make-interval
|
|
(ical:date-time-variant low :day 2)
|
|
(ical:date-time-variant low :day 3)))
|
|
(sub2 (icr:make-interval
|
|
(ical:date-time-variant low :month 12 :day 25)
|
|
(ical:date-time-variant low :month 12 :day 26)))
|
|
(expected-subintervals (list sub1 sub2)))
|
|
(should (equal expected-subintervals
|
|
(icr:refine-byyearday interval yeardays)))))
|
|
|
|
(ert-deftest ict:recur-refine-bymonth ()
|
|
"Does `icr:refine-bymonth' correctly refine by months?"
|
|
(let* ((low (ical:make-date-time :year 2025 :month 1 :day 1
|
|
:hour 0 :minute 0 :second 0))
|
|
(high (ical:date/time-add low :year 1))
|
|
(interval (icr:make-interval low high high))
|
|
(months (list 9 2))
|
|
(sub1 (icr:make-interval
|
|
(ical:date-time-variant low :month 2 :day 1)
|
|
(ical:date-time-variant low :month 3 :day 1)))
|
|
(sub2 (icr:make-interval
|
|
(ical:date-time-variant low :month 9 :day 1)
|
|
(ical:date-time-variant low :month 10 :day 1)))
|
|
(expected-subintervals (list sub1 sub2)))
|
|
(should (equal expected-subintervals
|
|
(icr:refine-bymonth interval months)))))
|
|
|
|
(ert-deftest ict:recur-refine-bymonthday ()
|
|
"Does `icr:refine-bymonthday' correctly refine by days of the month?"
|
|
(let* ((low (ical:make-date-time :year 2025 :month 2 :day 1
|
|
:hour 0 :minute 0 :second 0))
|
|
(high (ical:date/time-add low :month 1))
|
|
(interval (icr:make-interval low high high))
|
|
(monthdays (list -1 2 29))
|
|
;; N.B. we should get no subinterval for Feb. 29, 2025
|
|
(sub1 (icr:make-interval
|
|
(ical:date-time-variant low :day 2)
|
|
(ical:date-time-variant low :day 3)))
|
|
(sub2 (icr:make-interval
|
|
(ical:date-time-variant low :day 28)
|
|
(ical:date-time-variant low :month 3 :day 1)))
|
|
(expected-subintervals (list sub1 sub2)))
|
|
(should (equal expected-subintervals
|
|
(icr:refine-bymonthday interval monthdays)))))
|
|
|
|
(ert-deftest ict:recur-refine-byday ()
|
|
"Does `icr:refine-byday' correctly refine by days of the week?"
|
|
;; The simple case: just day names
|
|
(let* ((low (ical:make-date-time :year 2025 :month 3 :day 3 ; a Monday
|
|
:hour 0 :minute 0 :second 0))
|
|
(high (ical:date/time-add low :day 7))
|
|
(interval (icr:make-interval low high high))
|
|
(days (list 0 6)) ; just the weekend, please!
|
|
(sub1 (icr:make-interval
|
|
(ical:date-time-variant low :day 8)
|
|
(ical:date-time-variant low :day 9)))
|
|
(sub2 (icr:make-interval
|
|
(ical:date-time-variant low :day 9)
|
|
(ical:date-time-variant low :day 10)))
|
|
(expected-subintervals (list sub1 sub2)))
|
|
(should (equal expected-subintervals
|
|
(icr:refine-byday interval days))))
|
|
|
|
;; Day names with offsets within the month
|
|
(let* ((low (ical:make-date-time :year 2025 :month 3 :day 1 ; a Saturday
|
|
:hour 0 :minute 0 :second 0))
|
|
(high (ical:date/time-add low :month 1))
|
|
(interval (icr:make-interval low high high))
|
|
(days (list '(1 . 2) '(1 . -1))) ; second and last Monday
|
|
(sub1 (icr:make-interval
|
|
(ical:date-time-variant low :day 10)
|
|
(ical:date-time-variant low :day 11)))
|
|
(sub2 (icr:make-interval
|
|
(ical:date-time-variant low :day 31)
|
|
(ical:date-time-variant low :month 4 :day 1)))
|
|
(expected-subintervals (list sub1 sub2)))
|
|
(should (equal expected-subintervals
|
|
(icr:refine-byday interval days t))))
|
|
|
|
;; Day names with offsets within the year
|
|
(let* ((low (ical:make-date-time :year 2025 :month 1 :day 1
|
|
:hour 0 :minute 0 :second 0))
|
|
(high (ical:date/time-add low :year 1))
|
|
(interval (icr:make-interval low high high))
|
|
(days (list '(5 . 1) '(5 . -1))) ; first and last Friday
|
|
(sub1 (icr:make-interval
|
|
(ical:date-time-variant low :day 3)
|
|
(ical:date-time-variant low :day 4)))
|
|
(sub2 (icr:make-interval
|
|
(ical:date-time-variant low :month 12 :day 26)
|
|
(ical:date-time-variant low :month 12 :day 27)))
|
|
(expected-subintervals (list sub1 sub2)))
|
|
(should (equal expected-subintervals
|
|
(icr:refine-byday interval days nil)))))
|
|
|
|
(ert-deftest ict:recur-refine-byhour ()
|
|
"Does `icr:refine-byhour' correctly refine by hours?"
|
|
;; No time zone, just clock times:
|
|
(let* ((low (ical:make-date-time :year 2025 :month 1 :day 1
|
|
:hour 0 :minute 0 :second 0))
|
|
(high (ical:date/time-add low :day 1))
|
|
(interval (icr:make-interval low high high))
|
|
(hours (list 2 19))
|
|
(sub1 (icr:make-interval
|
|
(ical:date-time-variant low :hour 2)
|
|
(ical:date-time-variant low :hour 3)))
|
|
(sub2 (icr:make-interval
|
|
(ical:date-time-variant low :hour 19)
|
|
(ical:date-time-variant low :hour 20)))
|
|
(expected-subintervals (list sub1 sub2)))
|
|
(should (equal expected-subintervals
|
|
(icr:refine-byhour interval hours))))
|
|
|
|
;; With time zone, but without crossing an observance boundary:
|
|
(let* ((low (ical:make-date-time :year 2025 :month 2 :day 1
|
|
:hour 0 :minute 0 :second 0
|
|
:zone ict:est :dst nil))
|
|
(high (ical:date/time-add low :day 1 ict:tz-eastern))
|
|
(interval (icr:make-interval low high high))
|
|
(hours (list 2 19))
|
|
(sub1 (icr:make-interval
|
|
(ical:date-time-variant low :hour 2 :tz 'preserve)
|
|
(ical:date-time-variant low :hour 3 :tz 'preserve)))
|
|
(sub2 (icr:make-interval
|
|
(ical:date-time-variant low :hour 19 :tz 'preserve)
|
|
(ical:date-time-variant low :hour 20 :tz 'preserve)))
|
|
(expected-subintervals (list sub1 sub2)))
|
|
(should (equal expected-subintervals
|
|
(icr:refine-byhour interval hours ict:tz-eastern)))))
|
|
|
|
(ert-deftest ict:recur-refine-byminute ()
|
|
"Does `icr:refine-byminute' correctly refine by minutes?"
|
|
;; No time zone, just clock times:
|
|
(let* ((low (ical:make-date-time :year 2025 :month 5 :day 1
|
|
:hour 13 :minute 0 :second 0))
|
|
(high (ical:date/time-add low :hour 1))
|
|
(interval (icr:make-interval low high high))
|
|
(minutes (list 7 59))
|
|
(sub1 (icr:make-interval
|
|
(ical:date-time-variant low :minute 7)
|
|
(ical:date-time-variant low :minute 8)))
|
|
(sub2 (icr:make-interval
|
|
(ical:date-time-variant low :minute 59)
|
|
(ical:date-time-variant low :hour 14 :minute 0)))
|
|
(expected-subintervals (list sub1 sub2)))
|
|
(should (equal expected-subintervals
|
|
(icr:refine-byminute interval minutes))))
|
|
|
|
;; With time zone, but without crossing an observance boundary:
|
|
(let* ((low (ical:make-date-time :year 2025 :month 2 :day 1
|
|
:hour 13 :minute 0 :second 0
|
|
:zone ict:est :dst nil))
|
|
(high (ical:date/time-add low :hour 1 ict:tz-eastern))
|
|
(interval (icr:make-interval low high high))
|
|
(minutes (list 7 59))
|
|
(sub1 (icr:make-interval
|
|
(ical:date-time-variant low :minute 7 :tz 'preserve)
|
|
(ical:date-time-variant low :minute 8 :tz 'preserve)))
|
|
(sub2 (icr:make-interval
|
|
(ical:date-time-variant low :minute 59 :tz 'preserve)
|
|
(ical:date-time-variant low :hour 14 :minute 0 :tz 'preserve)))
|
|
(expected-subintervals (list sub1 sub2)))
|
|
(should (equal expected-subintervals
|
|
(icr:refine-byminute interval minutes ict:tz-eastern)))))
|
|
|
|
(ert-deftest ict:recur-refine-bysecond ()
|
|
"Does `icr:refine-bysecond' correctly refine by seconds?"
|
|
;; No time zone, just clock times:
|
|
(let* ((low (ical:make-date-time :year 2025 :month 5 :day 1
|
|
:hour 13 :minute 59 :second 0))
|
|
(high (ical:date/time-add low :minute 1))
|
|
(interval (icr:make-interval low high high))
|
|
(seconds (list 24 59))
|
|
(sub1 (icr:make-interval
|
|
(ical:date-time-variant low :second 24)
|
|
(ical:date-time-variant low :second 25)))
|
|
(sub2 (icr:make-interval
|
|
(ical:date-time-variant low :second 59)
|
|
(ical:date-time-variant low :hour 14 :minute 0 :second 0)))
|
|
(expected-subintervals (list sub1 sub2)))
|
|
(should (equal expected-subintervals
|
|
(icr:refine-bysecond interval seconds))))
|
|
|
|
;; With time zone, but without crossing an observance boundary:
|
|
(let* ((low (ical:make-date-time :year 2025 :month 2 :day 1
|
|
:hour 13 :minute 19 :second 0
|
|
:zone ict:est :dst nil))
|
|
(high (ical:date/time-add low :minute 1 ict:tz-eastern))
|
|
(interval (icr:make-interval low high high))
|
|
(seconds (list 24 59))
|
|
(sub1 (icr:make-interval
|
|
(ical:date-time-variant low :second 24 :tz 'preserve)
|
|
(ical:date-time-variant low :second 25 :tz 'preserve)))
|
|
(sub2 (icr:make-interval
|
|
(ical:date-time-variant low :second 59 :tz 'preserve)
|
|
(ical:date-time-variant low :minute 20 :second 0 :tz 'preserve)))
|
|
(expected-subintervals (list sub1 sub2)))
|
|
(should (equal expected-subintervals
|
|
(icr:refine-bysecond interval seconds ict:tz-eastern)))))
|
|
|
|
(ert-deftest ict:recur-subintervals-to-dates ()
|
|
"Does `icr:subintervals-to-dates' correctly generate recurrences?"
|
|
;; Two subintervals, the first three days long, the second less than a single day
|
|
(let* ((low1 (ical:make-date-time :year 2025 :month 5 :day 1
|
|
:hour 13 :minute 59 :second 0))
|
|
(high1 (ical:date/time-add low1 :day 3))
|
|
(sub1 (icr:make-interval low1 high1))
|
|
(low2 (ical:make-date-time :year 2025 :month 5 :day 31
|
|
:hour 14 :minute 0 :second 0))
|
|
(high2 (ical:date/time-add low2 :hour 3)) ; later but on the same day
|
|
(sub2 (icr:make-interval low2 high2))
|
|
(low-date1 (ical:date-time-to-date low1))
|
|
(low-date2 (ical:date-time-to-date low2))
|
|
(expected-recs (list low-date1
|
|
(ical:date/time-add low-date1 :day 1)
|
|
(ical:date/time-add low-date1 :day 2)
|
|
(ical:date/time-add low-date1 :day 3)
|
|
low-date2)))
|
|
(should (equal expected-recs
|
|
(icr:subintervals-to-dates (list sub1 sub2))))))
|
|
|
|
(ert-deftest ict:recur-subintervals-to-date-times ()
|
|
"Does `icr:subintervals-to-date-times' correctly generate recurrences?"
|
|
;; Two subintervals, each one second long, no time zone
|
|
(let* ((low1 (ical:make-date-time :year 2025 :month 5 :day 1
|
|
:hour 13 :minute 59 :second 0))
|
|
(high1 (ical:date/time-add low1 :second 1))
|
|
(sub1 (icr:make-interval low1 high1))
|
|
(low2 (ical:make-date-time :year 2025 :month 5 :day 2
|
|
:hour 14 :minute 0 :second 0))
|
|
(high2 (ical:date/time-add low2 :second 1))
|
|
(sub2 (icr:make-interval low2 high2))
|
|
(expected-recs (list low1 low2)))
|
|
(should (equal expected-recs
|
|
(icr:subintervals-to-date-times (list sub1 sub2)))))
|
|
|
|
;; A subinterval five seconds long, with time zone
|
|
(let* ((low1 (ical:make-date-time :year 2025 :month 6 :day 1
|
|
:hour 13 :minute 59 :second 0
|
|
:zone ict:edt :dst t))
|
|
(high1 (ical:date/time-add low1 :second 5 ict:tz-eastern))
|
|
(sub1 (icr:make-interval low1 high1))
|
|
(expected-recs
|
|
(list low1
|
|
(ical:date/time-add low1 :second 1 ict:tz-eastern)
|
|
(ical:date/time-add low1 :second 2 ict:tz-eastern)
|
|
(ical:date/time-add low1 :second 3 ict:tz-eastern)
|
|
(ical:date/time-add low1 :second 4 ict:tz-eastern))))
|
|
(should (equal expected-recs
|
|
(icr:subintervals-to-date-times (list sub1) ict:tz-eastern))))
|
|
|
|
;; A subinterval five seconds long, with time zone, which crosses an
|
|
;; observance boundary where the final three seconds occur after
|
|
;; clocks are set forward an hour; these seconds should therefore be in EDT:
|
|
(let* ((low1 (ical:make-date-time :year 2025 :month 3 :day 9
|
|
:hour 1 :minute 59 :second 58
|
|
:zone ict:est :dst nil))
|
|
(high1 (ical:make-date-time :year 2025 :month 3 :day 9
|
|
:hour 3 :minute 0 :second 3
|
|
:zone ict:edt :dst t))
|
|
(sub1 (icr:make-interval low1 high1))
|
|
(expected-recs
|
|
(list low1
|
|
(ical:date-time-variant low1 :second 59 :tz 'preserve)
|
|
(ical:date-time-variant high1 :second 0 :tz 'preserve)
|
|
(ical:date-time-variant high1 :second 1 :tz 'preserve)
|
|
(ical:date-time-variant high1 :second 2 :tz 'preserve))))
|
|
(should (equal expected-recs
|
|
(icr:subintervals-to-date-times (list sub1) ict:tz-eastern))))
|
|
|
|
;; A subinterval five seconds long, with time zone, which crosses an
|
|
;; observance boundary where the final three seconds occur after
|
|
;; clocks are set back an hour; these seconds should therefore be in
|
|
;; EST:
|
|
(let* ((low1 (ical:make-date-time :year 2024 :month 11 :day 3
|
|
:hour 1 :minute 59 :second 58
|
|
:zone ict:edt :dst t))
|
|
(high1 (ical:make-date-time :year 2024 :month 11 :day 3
|
|
:hour 1 :minute 0 :second 2
|
|
:zone ict:est :dst nil))
|
|
(sub1 (icr:make-interval low1 high1))
|
|
(expected-recs
|
|
(list low1
|
|
(ical:date-time-variant low1 :second 59 :tz 'preserve)
|
|
(ical:date-time-variant high1 :second 0 :tz 'preserve)
|
|
(ical:date-time-variant high1 :second 1 :tz 'preserve))))
|
|
(should (equal expected-recs
|
|
(icr:subintervals-to-date-times (list sub1) ict:tz-eastern)))))
|
|
|
|
;; Tests for time zone functions:
|
|
|
|
(ert-deftest ict:recur-tz-observance-on/nonexistent ()
|
|
"Does `icr:tz-observance-on' correctly interpret nonexistent times?"
|
|
(let* ((onset-start (ical:make-date-time :year 2030 :month 3 :day 10
|
|
:hour 2 :minute 0 :second 0
|
|
:zone ict:est :dst nil))
|
|
(start-shifted (ical:date-time-variant onset-start :hour 3
|
|
:zone ict:edt :dst t))
|
|
;; 2:30AM falls into the gap when the clock jumps from 2AM to 3AM:
|
|
(nonexistent (ical:date-time-variant onset-start :minute 30
|
|
:zone ict:est :dst nil))
|
|
(nonexistent-shifted (ical:date-time-variant nonexistent :hour 3
|
|
:zone ict:edt :dst t)))
|
|
(icr:tz-observance-on onset-start ict:tz-eastern t) ;; updates the time to EDT
|
|
(icr:tz-observance-on nonexistent ict:tz-eastern t) ;; updates the time to EDT
|
|
(should (equal onset-start start-shifted))
|
|
(should (equal nonexistent nonexistent-shifted))))
|
|
|
|
(ert-deftest ict:recur-tz-observance-on/occurs-twice ()
|
|
"Does `icr:tz-observance-on' correctly interpret times that occur twice?"
|
|
(let* ((onset-start (ical:make-date-time :year 2025 :month 11 :day 2
|
|
:hour 2 :minute 0 :second 0
|
|
:zone ict:edt :dst t))
|
|
;; 1:30AM occurs twice when the clock is set back from 2AM to 1AM:
|
|
(no-zone (ical:date-time-variant onset-start :hour 1 :minute 30))
|
|
(first (ical:date-time-variant onset-start :hour 1 :minute 30
|
|
:zone ict:edt :dst t))
|
|
(second (ical:date-time-variant first :zone ict:est :dst nil))
|
|
(first+1h (ical:date/time-add first :hour 1 ict:tz-eastern)))
|
|
(icr:tz-observance-on no-zone ict:tz-eastern t) ;; sets zone
|
|
(should (equal first no-zone))
|
|
(should (equal second first+1h))))
|
|
|
|
(ert-deftest ict:recur-tz-observance-on ()
|
|
"Does `icr:tz-observance-on' correctly find observances?"
|
|
|
|
;; A date before the start of all observances in the timezone.
|
|
;; In this case, there is no matching observance, so we should get nil.
|
|
(let* ((dt (ical:make-date-time :year 1900 :month 1 :day 1
|
|
:hour 12 :minute 0 :second 0
|
|
:zone ict:est :dst nil))
|
|
(ts (encode-time dt)))
|
|
(should (null (icr:tz-observance-on dt ict:tz-eastern)))
|
|
(should (null (icr:tz-observance-on ts ict:tz-eastern))))
|
|
|
|
;; A date matching the start of one of the STANDARD observances:
|
|
(let* ((dt (ical:make-date-time :year 1967 :month 10 :day 29
|
|
:hour 2 :minute 0 :second 0
|
|
:zone ict:edt :dst t))
|
|
(obs/onset (icr:tz-observance-on dt ict:tz-eastern))
|
|
(obs (car obs/onset))
|
|
(onset (cadr obs/onset))
|
|
;; make sure we get the same result with an absolute time:
|
|
(ts (encode-time dt))
|
|
(ts-obs/onset (icr:tz-observance-on ts ict:tz-eastern)))
|
|
(should (eq 'ical:standard (ical:ast-node-type obs)))
|
|
(should (equal dt onset))
|
|
(should (equal obs/onset ts-obs/onset)))
|
|
|
|
;; A date matching the start of a DAYLIGHT observance:
|
|
(let* ((dt (ical:make-date-time :year 1967 :month 4 :day 30
|
|
:hour 2 :minute 0 :second 0
|
|
:zone ict:est :dst nil))
|
|
(obs/onset (icr:tz-observance-on dt ict:tz-eastern))
|
|
(obs (car obs/onset))
|
|
(onset (cadr obs/onset))
|
|
;; make sure we get the same result with an absolute time:
|
|
(ts (encode-time dt))
|
|
(ts-obs/onset (icr:tz-observance-on ts ict:tz-eastern)))
|
|
(should (eq 'ical:daylight (ical:ast-node-type obs)))
|
|
(should (equal dt onset))
|
|
(should (equal obs/onset ts-obs/onset)))
|
|
|
|
;; A date matching an RDATE of a DAYLIGHT observance:
|
|
(let* ((dt (ical:make-date-time :year 1975 :month 2 :day 23
|
|
:hour 2 :minute 0 :second 0
|
|
:zone ict:est :dst nil))
|
|
(obs/onset (icr:tz-observance-on dt ict:tz-eastern))
|
|
(obs (car obs/onset))
|
|
(onset (cadr obs/onset))
|
|
;; make sure we get the same result with an absolute time:
|
|
(ts (encode-time dt))
|
|
(ts-obs/onset (icr:tz-observance-on ts ict:tz-eastern)))
|
|
(should (eq 'ical:daylight (ical:ast-node-type obs)))
|
|
(should (equal dt onset))
|
|
(should (equal obs/onset ts-obs/onset)))
|
|
|
|
;; A date matching the end of a STANDARD observance:
|
|
(let* ((ut (ical:make-date-time :year 2006 :month 10 :day 29
|
|
:hour 6 :minute 0 :second 0
|
|
:zone 0 :dst nil)) ; UNTIL is in UTC
|
|
(dt (ical:make-date-time :year 2006 :month 10 :day 29
|
|
:hour 2 :minute 0 :second 0
|
|
:zone ict:edt :dst t))
|
|
(obs/onset (icr:tz-observance-on dt ict:tz-eastern))
|
|
(obs (car obs/onset))
|
|
(onset (cadr obs/onset))
|
|
;; make sure we get the same result with an absolute time:
|
|
(ts (encode-time dt))
|
|
(ts-obs/onset (icr:tz-observance-on ts ict:tz-eastern)))
|
|
(should (ical:date-time-simultaneous-p ut dt))
|
|
(should (eq 'ical:standard (ical:ast-node-type obs)))
|
|
(should (equal dt onset))
|
|
(should (equal obs/onset ts-obs/onset)))
|
|
|
|
;; A date matching the end of a DAYLIGHT observance:
|
|
(let* ((ut (ical:make-date-time :year 2006 :month 4 :day 2
|
|
:hour 7 :minute 0 :second 0
|
|
:zone 0 :dst nil)) ; UNTIL is in UTC
|
|
(dt (ical:make-date-time :year 2006 :month 4 :day 2
|
|
:hour 2 :minute 0 :second 0
|
|
:zone ict:est :dst nil))
|
|
(obs/onset (icr:tz-observance-on dt ict:tz-eastern))
|
|
(obs (car obs/onset))
|
|
(onset (cadr obs/onset))
|
|
;; make sure we get the same result with an absolute time:
|
|
(ts (encode-time dt))
|
|
(ts-obs/onset (icr:tz-observance-on ts ict:tz-eastern)))
|
|
(should (ical:date-time-simultaneous-p ut dt))
|
|
(should (eq 'ical:daylight (ical:ast-node-type obs)))
|
|
(should (equal dt onset))
|
|
(should (equal obs/onset ts-obs/onset)))
|
|
|
|
;; A date matching an onset in the middle of a DAYLIGHT observance
|
|
;; which has ended:
|
|
(let* ((dt (ical:make-date-time :year 1980 :month 4 :day 27
|
|
:hour 2 :minute 0 :second 0
|
|
:zone ict:est :dst nil))
|
|
(end (ical:make-date-time :year 1986 :month 4 :day 27
|
|
:hour 7 :minute 0 :second 0
|
|
:zone 0)) ; UNTIL is in UTC
|
|
(obs/onset (icr:tz-observance-on dt ict:tz-eastern))
|
|
(obs (car obs/onset))
|
|
(onset (cadr obs/onset))
|
|
;; make sure we get the same result with an absolute time:
|
|
(ts (encode-time dt))
|
|
(ts-obs/onset (icr:tz-observance-on ts ict:tz-eastern)))
|
|
(should (eq 'ical:daylight (ical:ast-node-type obs)))
|
|
(should (equal dt onset))
|
|
(should (equal end (ical:rrule-until
|
|
(ical:with-property-of obs 'ical:rrule nil value))))
|
|
(should (equal obs/onset ts-obs/onset)))
|
|
|
|
;; A date matching an onset of the DAYLIGHT observance which is
|
|
;; ongoing:
|
|
(let* ((dt (ical:make-date-time :year 2025 :month 3 :day 9
|
|
:hour 2 :minute 0 :second 0
|
|
:zone ict:est :dst nil))
|
|
(obs/onset (icr:tz-observance-on dt ict:tz-eastern))
|
|
(obs (car obs/onset))
|
|
(onset (cadr obs/onset))
|
|
;; make sure we get the same result with an absolute time:
|
|
(ts (encode-time dt))
|
|
(ts-obs/onset (icr:tz-observance-on ts ict:tz-eastern)))
|
|
(should (eq 'ical:daylight (ical:ast-node-type obs)))
|
|
(should (equal dt onset))
|
|
(should (equal obs/onset ts-obs/onset)))
|
|
|
|
;; A date in the middle of the DAYLIGHT observance which is ongoing:
|
|
(let* ((start (ical:make-date-time :year 2025 :month 3 :day 9
|
|
:hour 2 :minute 0 :second 0
|
|
:zone ict:est :dst nil))
|
|
(dt (ical:make-date-time :year 2025 :month 5 :day 28
|
|
:hour 2 :minute 0 :second 0
|
|
:zone ict:edt :dst t))
|
|
(obs/onset (icr:tz-observance-on dt ict:tz-eastern))
|
|
(obs (car obs/onset))
|
|
(onset (cadr obs/onset))
|
|
;; make sure we get the same result with an absolute time:
|
|
(ts (encode-time dt))
|
|
(ts-obs/onset (icr:tz-observance-on ts ict:tz-eastern)))
|
|
(should (eq 'ical:daylight (ical:ast-node-type obs)))
|
|
(should (equal start onset))
|
|
(should (equal obs/onset ts-obs/onset)))
|
|
|
|
;; A date in the middle of the STANDARD observance which is ongoing:
|
|
(let* ((start (ical:make-date-time :year 2025 :month 11 :day 2
|
|
:hour 2 :minute 0 :second 0
|
|
:zone ict:edt :dst t))
|
|
(dt (ical:make-date-time :year 2026 :month 1 :day 28
|
|
:hour 12 :minute 30 :second 0
|
|
:zone ict:est :dst nil))
|
|
(obs/onset (icr:tz-observance-on dt ict:tz-eastern))
|
|
(obs (car obs/onset))
|
|
(onset (cadr obs/onset))
|
|
;; make sure we get the same result with an absolute time:
|
|
(ts (encode-time dt))
|
|
(ts-obs/onset (icr:tz-observance-on ts ict:tz-eastern)))
|
|
(should (eq 'ical:standard (ical:ast-node-type obs)))
|
|
(should (equal start onset))
|
|
(should (equal obs/onset ts-obs/onset)))
|
|
|
|
;; The following two tests were useful in detecting a broken optimization:
|
|
(let* ((start (ical:make-date-time :year 2006 :month 10 :day 29
|
|
:hour 2 :minute 0 :second 0
|
|
:zone ict:edt :dst t))
|
|
(dt (ical:make-date-time :year 2006 :month 11 :day 1
|
|
:hour 12 :minute 30 :second 0
|
|
:zone ict:est :dst nil))
|
|
(obs/onset (icr:tz-observance-on dt ict:tz-eastern))
|
|
(obs (car obs/onset))
|
|
(onset (cadr obs/onset))
|
|
;; make sure we get the same result with an absolute time:
|
|
(ts (encode-time dt))
|
|
(ts-obs/onset (icr:tz-observance-on ts ict:tz-eastern)))
|
|
(should (eq 'ical:standard (ical:ast-node-type obs)))
|
|
(should (equal start onset))
|
|
(should (equal obs/onset ts-obs/onset)))
|
|
|
|
(let* ((start (ical:make-date-time :year 2007 :month 11 :day 4
|
|
:hour 2 :minute 0 :second 0
|
|
:zone ict:edt :dst t))
|
|
(dt (ical:make-date-time :year 2008 :month 2 :day 1
|
|
:hour 12 :minute 30 :second 0
|
|
:zone ict:est :dst nil))
|
|
(obs/onset (icr:tz-observance-on dt ict:tz-eastern))
|
|
(obs (car obs/onset))
|
|
(onset (cadr obs/onset))
|
|
;; make sure we get the same result with an absolute time:
|
|
(ts (encode-time dt))
|
|
(ts-obs/onset (icr:tz-observance-on ts ict:tz-eastern)))
|
|
(should (eq 'ical:standard (ical:ast-node-type obs)))
|
|
(should (equal start onset))
|
|
(should (equal obs/onset ts-obs/onset)))
|
|
|
|
|
|
;; A date in the middle of the STANDARD observance which is ongoing;
|
|
;; test that the update flag correctly sets the zone information:
|
|
(let* ((start (ical:make-date-time :year 2025 :month 11 :day 2
|
|
:hour 2 :minute 0 :second 0
|
|
:zone ict:edt :dst t))
|
|
(dt (ical:make-date-time :year 2026 :month 1 :day 28
|
|
:hour 12 :minute 30 :second 0
|
|
;; no zone information
|
|
))
|
|
(obs/onset (icr:tz-observance-on dt ict:tz-eastern t))
|
|
(obs (car obs/onset))
|
|
(onset (cadr obs/onset)))
|
|
(should (eq 'ical:standard (ical:ast-node-type obs)))
|
|
(should (equal start onset))))
|
|
|
|
|
|
;; Tests for recurrence rule interpretation:
|
|
(cl-defmacro ict:rrule-test (recur-string doc
|
|
&key dtstart
|
|
(low dtstart)
|
|
high
|
|
tz
|
|
rdates
|
|
exdates
|
|
members
|
|
nonmembers
|
|
size
|
|
(tags nil)
|
|
source)
|
|
|
|
"Create a test which parses RECUR-STRING to an `icalendar-recur',
|
|
creates an event with a recurrence set from this value, and checks
|
|
various properties of the recurrence set.
|
|
|
|
DTSTART should be an `icalendar-date' or `icalendar-date-time'
|
|
value appropriate to the RECUR-STRING. The value will be
|
|
bound to the symbol `dtstart'; this symbol can thus be used inside
|
|
the expressions for MEMBERS and NONMEMBERS.
|
|
LOW and HIGH should be the bounds of the window in which to compute
|
|
recurrences. LOW defaults to DTSTART.
|
|
TZ, if present, should be an `icalendar-vtimezone'.
|
|
Date-times in the recurrence set will be calculated relative to this
|
|
time zone.
|
|
RDATES, if present, should be a list of additional
|
|
`icalendar-date' or `icalendar-date-time' values to be added to
|
|
the recurrence set *in addition to* those generated by the
|
|
recurrence rule (see `icalendar-rdate').
|
|
EXDATES, if present, should be a list of `icalendar-date' or
|
|
`icalendar-date-time' values to be excluded from the recurrence
|
|
set, *even if* they are in RDATES or generated by the
|
|
recurrence rule (see `icalendar-exdate').
|
|
MEMBERS, if present, should be a list of values that are expected
|
|
to be present in the recurrence set.
|
|
NONMEMBERS, if present, should be a list of values that are expected
|
|
to be excluded from the recurrence set.
|
|
SIZE, if present, should be a positive integer representing the
|
|
expected size of the recurrence set. Defaults to the value of the
|
|
COUNT clause in the recurrence rule, if any.
|
|
TAGS is passed on to `ert-deftest'.
|
|
SOURCE should be a symbol; it is used to name the test."
|
|
`(ert-deftest ,(intern (concat "ict:rrule-test-" (symbol-name source))) ()
|
|
,(format "Parse and evaluate recur-value example from `%s':\n%s"
|
|
source doc)
|
|
:tags ,tags
|
|
(let* ((parsed (ical:parse-from-string 'ical:rrule-value ,recur-string))
|
|
(recvalue (ical:ast-node-value parsed))
|
|
(until (ical:rrule-until recvalue))
|
|
(count (ical:rrule-count recvalue))
|
|
(dtstart ,dtstart)
|
|
(tzid
|
|
(when (cl-typep dtstart 'ical:date-time)
|
|
"America/New_York"))
|
|
(recset-size (or ,size count))
|
|
(vevent
|
|
(ical:make-vevent
|
|
(ical:uid (concat "uid-test-" ,(symbol-name source)))
|
|
(ical:dtstart dtstart (ical:tzidparam tzid))
|
|
(ical:rrule parsed)
|
|
(ical:rdate ,rdates)
|
|
(ical:exdate ,exdates)))
|
|
;; default for HIGH: UNTIL or DTSTART+3*INTERVAL
|
|
(win-high
|
|
(or ,high
|
|
until
|
|
(icr:interval-high
|
|
(icr:nth-interval 2 ,dtstart recvalue))))
|
|
(recs
|
|
(if count
|
|
(icr:recurrences-to-count vevent ,tz)
|
|
(icr:recurrences-in-window ,low win-high vevent ,tz))))
|
|
(should (ical:ast-node-valid-p parsed))
|
|
(when ,members
|
|
(dolist (dt ,members)
|
|
(should (member dt recs))))
|
|
(when ,nonmembers
|
|
(dolist (dt ,nonmembers)
|
|
(should-not (member dt recs))))
|
|
(when recset-size
|
|
(should (length= recs recset-size))))))
|
|
|
|
(ict:rrule-test
|
|
"FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1"
|
|
"Last non-weekend day of the month"
|
|
:dtstart '(3 31 2025)
|
|
:high '(6 1 2025)
|
|
:members '((3 31 2025) (4 30 2025) (5 30 2025))
|
|
:nonmembers '((5 31 2025)) ;; 5/31/2025 is a Saturday
|
|
:source rfc5545-sec3.3.10/1)
|
|
|
|
(ict:rrule-test
|
|
"FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30"
|
|
"Every Sunday in January at 8:30AM and 9:30AM, every other year"
|
|
:dtstart (ical:read-date-time "20250105T083000")
|
|
:high (ical:read-date-time "20271231T000000")
|
|
:members
|
|
(let ((jan3-27 (ical:make-date-time :year 2027 :month 1 :day 3
|
|
:hour 8 :minute 30 :second 0)))
|
|
(list dtstart
|
|
;; 2025: Jan 5, 12, 19, 26
|
|
(ical:date-time-variant dtstart :hour 9)
|
|
(ical:date-time-variant dtstart :day 12)
|
|
(ical:date-time-variant dtstart :day 12 :hour 9)
|
|
(ical:date-time-variant dtstart :day 19)
|
|
(ical:date-time-variant dtstart :day 19 :hour 9)
|
|
(ical:date-time-variant dtstart :day 19)
|
|
(ical:date-time-variant dtstart :day 19 :hour 9)
|
|
(ical:date-time-variant dtstart :day 26)
|
|
(ical:date-time-variant dtstart :day 26 :hour 9)
|
|
;; 2027: Jan 3, 10, 17, 24, 31
|
|
(ical:date-time-variant jan3-27 :hour 9)
|
|
(ical:date-time-variant jan3-27 :day 10)
|
|
(ical:date-time-variant jan3-27 :day 10 :hour 9)
|
|
(ical:date-time-variant jan3-27 :day 17)
|
|
(ical:date-time-variant jan3-27 :day 17 :hour 9)
|
|
(ical:date-time-variant jan3-27 :day 24)
|
|
(ical:date-time-variant jan3-27 :day 24 :hour 9)
|
|
(ical:date-time-variant jan3-27 :day 31)
|
|
(ical:date-time-variant jan3-27 :day 31 :hour 9)))
|
|
:nonmembers
|
|
(list
|
|
(ical:make-date-time :year 2026 :month 1 :day 4
|
|
:hour 8 :minute 30 :second 0)
|
|
(ical:make-date-time :year 2026 :month 1 :day 4
|
|
:hour 9 :minute 30 :second 0))
|
|
:source rfc5545-sec3.3.10/2)
|
|
|
|
(ict:rrule-test
|
|
"FREQ=YEARLY;BYMONTH=2;BYMONTHDAY=-1"
|
|
"Every year on the last day in February"
|
|
:dtstart '(2 29 2024)
|
|
:high '(3 1 2028)
|
|
:members '((2 28 2025) (2 28 2026) (2 28 2027) (2 29 2028))
|
|
:nonmembers '((2 28 2028))
|
|
:source leap-day/1)
|
|
|
|
(ict:rrule-test
|
|
"FREQ=YEARLY;INTERVAL=4;BYMONTH=2;BYMONTHDAY=29"
|
|
"Every four years on February 29"
|
|
:dtstart '(2 29 2024)
|
|
:high '(3 1 2028)
|
|
:members '((2 29 2028))
|
|
:nonmembers '((2 28 2028))
|
|
:source leap-day/2)
|
|
|
|
(ict:rrule-test
|
|
"FREQ=DAILY;COUNT=10"
|
|
"Daily for 10 occurrences"
|
|
:dtstart (ical:make-date-time :year 1997 :month 9 :day 2
|
|
:hour 9 :minute 0 :second 0)
|
|
:members
|
|
;; (1997 9:00 AM EDT) September 2-11
|
|
(mapcar
|
|
(lambda (day) (ical:date-time-variant dtstart :day day))
|
|
(number-sequence 2 11))
|
|
:source rfc5545-sec3.3.10/3)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=YEARLY"
|
|
"Every year on a specific date, e.g. an anniversary"
|
|
:dtstart '(11 11 2024)
|
|
:high '(10 1 2030)
|
|
:members '((11 11 2024)
|
|
(11 11 2025)
|
|
(11 11 2026)
|
|
(11 11 2027)
|
|
(11 11 2028)
|
|
(11 11 2029))
|
|
:nonmembers '((11 11 2030))
|
|
:source rfc5545-sec3.6.1/3)
|
|
|
|
;; Time zone tests
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=-1SU;UNTIL=19730429T070000Z"
|
|
"Every year on the last Sunday of April (through 1973-04-29) at 2AM.
|
|
(Onset of US Eastern Daylight Time.)"
|
|
:tz ict:tz-eastern
|
|
;; DTSTART and all the times below are at *3*AM EDT, because 2AM EST
|
|
;; (the onset of the observance) does not exist as a local time:
|
|
:dtstart (ical:make-date-time :year 1967 :month 4 :day 30
|
|
:hour 3 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:high (ical:date-time-variant dtstart :year 1973 :month 4 :day 30
|
|
:zone ict:edt :dst t)
|
|
:members
|
|
(list
|
|
(ical:date-time-variant dtstart :year 1968 :day 28 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :year 1969 :day 27 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :year 1970 :day 26 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :year 1971 :day 25 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :year 1972 :day 30 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :year 1973 :day 29 :tz 'preserve))
|
|
:source rfc5545-sec3.6.5/1)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU"
|
|
"Every year on the first Sunday of November at 2AM.
|
|
(Onset of Eastern Standard Time)."
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 2007 :month 11 :day 4
|
|
:hour 2 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:high (ical:date-time-variant dtstart :year 2010 :month 11 :day 8
|
|
:zone ict:est :dst nil)
|
|
:members
|
|
;; all the times below are at *1*AM EST, because 2AM EDT (the onset of
|
|
;; the observance) is when clocks get set back:
|
|
(list (ical:date-time-variant dtstart
|
|
:year 2008 :month 11 :day 2
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart
|
|
:year 2009 :month 11 :day 1
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart
|
|
:year 2010 :month 11 :day 7
|
|
:zone ict:est :dst nil))
|
|
:source rfc5545-sec3.6.5/3.1)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=MONTHLY;INTERVAL=3;BYDAY=1SU"
|
|
"Every three months on the first Sunday of the month."
|
|
:dtstart '(1 5 2025)
|
|
:high '(1 1 2026)
|
|
:members (list '(4 6 2025)
|
|
'(7 6 2025)
|
|
'(10 5 2025))
|
|
:nonmembers (list '(1 12 2025) ;; second Sun.
|
|
'(2 2 2025) ;; first Sun. in Feb.
|
|
'(4 5 2025)) ;; Sat.
|
|
:source monthly/interval)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=DAILY;COUNT=10\n"
|
|
"Daily for 10 occurrences"
|
|
:dtstart (ical:read-date-time "19970902T090000")
|
|
:members
|
|
(mapcar
|
|
(lambda (day) (ical:date-time-variant dtstart :day day))
|
|
(number-sequence 2 11))
|
|
:nonmembers (list (ical:date-time-variant dtstart :day 12))
|
|
:high (ical:read-date-time "19970912T090000")
|
|
:source rfc5545-sec3.8.5.3/1)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=DAILY;UNTIL=19971224T000000Z\n"
|
|
"Daily at 9AM until December 24, 1997"
|
|
:tags '(:expensive-test)
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 9 :day 2
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:members
|
|
(append
|
|
;; EDT:
|
|
(mapcar
|
|
(lambda (day) (ical:date-time-variant dtstart :day day :tz 'preserve))
|
|
(number-sequence 2 30)) ;; Sept. 2--30
|
|
(mapcar
|
|
(lambda (day) (ical:date-time-variant dtstart :month 10 :day day
|
|
:tz 'preserve))
|
|
(number-sequence 1 25)) ;; Oct. 1--25
|
|
;; EST:
|
|
(mapcar
|
|
(lambda (day)
|
|
(ical:date-time-variant dtstart :month 10 :day day :zone ict:est :dst nil))
|
|
(number-sequence 26 31))) ;; Oct. 26--31
|
|
:source rfc5545-sec3.8.5.3/2)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=DAILY;INTERVAL=2\n"
|
|
"Every other day - forever"
|
|
:tags '(:expensive-test)
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 9 :day 2
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:high (ical:make-date-time :year 1997 :month 12 :day 4
|
|
:hour 0 :minute 0 :second 0
|
|
:zone ict:est :dst nil)
|
|
:members
|
|
(append
|
|
;; (1997 9:00 AM EDT) September 2,4,6,8...24,26,28,30;
|
|
;; October 2,4,6...20,22,24
|
|
(mapcar
|
|
(lambda (n)
|
|
(ical:date-time-variant dtstart :day (* 2 n) :tz 'preserve))
|
|
(number-sequence 1 15))
|
|
(mapcar
|
|
(lambda (n)
|
|
(ical:date-time-variant dtstart :month 10 :day (* 2 n) :tz 'preserve))
|
|
(number-sequence 1 12))
|
|
;; (1997 9:00 AM EST) October 26,28,30;
|
|
;; November 1,3,5,7...25,27,29;
|
|
;; December 1,3,...
|
|
(mapcar
|
|
(lambda (n)
|
|
(ical:date-time-variant dtstart :month 10 :day (* 2 n)
|
|
:zone ict:est :dst nil))
|
|
(number-sequence 13 15))
|
|
(mapcar
|
|
(lambda (n)
|
|
(ical:date-time-variant dtstart :month 11 :day (1- (* 2 n))
|
|
:zone ict:est :dst nil))
|
|
(number-sequence 1 15))
|
|
(mapcar
|
|
(lambda (n)
|
|
(ical:date-time-variant dtstart :month 12 :day (1- (* 2 n))
|
|
:zone ict:est :dst nil))
|
|
(number-sequence 1 2)))
|
|
|
|
:nonmembers
|
|
(list
|
|
;; e.g.
|
|
(ical:make-date-time :year 1997 :month 10 :day 27
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:est :dst nil))
|
|
:source rfc5545-sec3.8.5.3/3)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=DAILY;INTERVAL=10;COUNT=5\n"
|
|
"Every ten days for five recurrences"
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 9 :day 2
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
|
|
:members ;; (1997 9:00 AM EDT) September 2,12,22; October 2,12
|
|
(list
|
|
dtstart
|
|
(ical:make-date-time :year 1997 :month 9 :day 12
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
(ical:make-date-time :year 1997 :month 9 :day 22
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
(ical:make-date-time :year 1997 :month 10 :day 2
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
(ical:make-date-time :year 1997 :month 10 :day 12
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t))
|
|
:source rfc5545-sec3.8.5.3/4)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=YEARLY;UNTIL=20000131T140000Z;BYMONTH=1;BYDAY=SU,MO,TU,WE,TH,FR,SA\n"
|
|
"Every day in January, for three years (weekdays explicit)"
|
|
:tags '(:expensive-test)
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1998 :month 1 :day 1
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:est :dst nil)
|
|
:high (ical:make-date-time :year 2000 :month 2 :day 1
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:est :dst nil)
|
|
:members
|
|
;; (1998 9:00 AM EST)January 1-31
|
|
;; (1999 9:00 AM EST)January 1-31
|
|
;; (2000 9:00 AM EST)January 1-31
|
|
(append
|
|
(mapcar
|
|
(lambda (day) (ical:date-time-variant dtstart :day day :tz 'preserve))
|
|
(number-sequence 1 31))
|
|
(mapcar
|
|
(lambda (day)
|
|
(ical:date-time-variant dtstart :year 1999 :day day :tz 'preserve))
|
|
(number-sequence 1 31))
|
|
(mapcar
|
|
(lambda (day)
|
|
(ical:date-time-variant dtstart :year 2000 :day day :tz 'preserve))
|
|
(number-sequence 1 31)))
|
|
:source rfc5545-sec3.8.5.3/5)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=DAILY;UNTIL=20000131T140000Z;BYMONTH=1\n"
|
|
"Every day in January, for three years (weekdays implicit)"
|
|
:tags '(:expensive-test)
|
|
;; TODO: as things are currently implemented, this way of expressing
|
|
;; the rule is quite expensive, since we end up computing intervals and
|
|
;; recurrences for every day of the year, even though the only relevant
|
|
;; days are in January and there are no recurrences on the other days.
|
|
;; We could try to optimize e.g. icr:refine-from-clauses to deal with such
|
|
;; cases.
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1998 :month 1 :day 1
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:est :dst nil)
|
|
:high (ical:make-date-time :year 2000 :month 2 :day 1
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:est :dst nil)
|
|
:members
|
|
;; (1998 9:00 AM EST)January 1-31
|
|
;; (1999 9:00 AM EST)January 1-31
|
|
;; (2000 9:00 AM EST)January 1-31
|
|
(append
|
|
(mapcar
|
|
(lambda (day) (ical:date-time-variant dtstart :day day :tz 'preserve))
|
|
(number-sequence 1 31))
|
|
(mapcar
|
|
(lambda (day)
|
|
(ical:date-time-variant dtstart :year 1999 :day day :tz 'preserve))
|
|
(number-sequence 1 31))
|
|
(mapcar
|
|
(lambda (day)
|
|
(ical:date-time-variant dtstart :year 2000 :day day :tz 'preserve))
|
|
(number-sequence 1 31)))
|
|
:source rfc5545-sec3.8.5.3/6)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=WEEKLY;COUNT=10\n"
|
|
"Weekly for ten occurrences"
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 9 :day 2
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:members
|
|
(append
|
|
;; (1997 9:00 AM EDT) September 2,9,16,23,30;October 7,14,21
|
|
(mapcar
|
|
(lambda (day)
|
|
(ical:date-time-variant dtstart :day day :tz 'preserve))
|
|
(list 2 9 16 23 30))
|
|
(mapcar
|
|
(lambda (day)
|
|
(ical:date-time-variant dtstart :month 10 :day day :tz 'preserve))
|
|
(list 7 14 21))
|
|
;; (1997 9:00 AM EST) October 28;November 4
|
|
(list
|
|
(ical:make-date-time :year 1997 :month 10 :day 28
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:est :dst nil)
|
|
(ical:make-date-time :year 1997 :month 11 :day 4
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:est :dst nil)))
|
|
:source rfc5545-sec3.8.5.3/7)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=WEEKLY;UNTIL=19971224T000000Z\n"
|
|
"Every week until December 24, 1997"
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 9 :day 2
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:members
|
|
(let ((oct97 (ical:date-time-variant dtstart :month 10
|
|
:zone ict:edt :dst t))
|
|
(nov97 (ical:date-time-variant dtstart :month 11
|
|
:zone ict:est :dst nil))
|
|
(dec97 (ical:date-time-variant dtstart :month 12
|
|
:zone ict:est :dst nil)))
|
|
(append
|
|
;; (1997 9:00 AM EDT) September 2,9,16,23,30;
|
|
;; October 7,14,21
|
|
(mapcar
|
|
(lambda (day)
|
|
(ical:date-time-variant dtstart :day day :tz 'preserve))
|
|
(list 2 9 16 23 30))
|
|
(mapcar
|
|
(lambda (day)
|
|
(ical:date-time-variant oct97 :day day :tz 'preserve))
|
|
(list 7 14 21))
|
|
;; (1997 9:00 AM EST) October 28;
|
|
;; November 4,11,18,25;
|
|
;; December 2,9,16,23
|
|
(list (ical:date-time-variant oct97 :day 28 :zone ict:est :dst nil))
|
|
(mapcar
|
|
(lambda (day)
|
|
(ical:date-time-variant nov97 :day day :tz 'preserve))
|
|
(list 4 11 18 25))
|
|
(mapcar
|
|
(lambda (day)
|
|
(ical:date-time-variant dec97 :day day :tz 'preserve))
|
|
(list 2 9 16 23))))
|
|
:source rfc5545-sec3.8.5.3/8)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=WEEKLY;INTERVAL=2;WKST=SU\n"
|
|
"Every other week - forever; Weekstart on Sunday"
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 9 :day 2
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:high (ical:make-date-time :year 1998 :month 3 :day 1
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:est :dst nil)
|
|
:members
|
|
(list
|
|
;; ==> (1997 9:00 AM EDT) September 2,16,30;
|
|
;; October 14
|
|
dtstart
|
|
(ical:date-time-variant dtstart :day 16 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 30 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :month 10 :day 14 :tz 'preserve)
|
|
;; (1997 9:00 AM EST) October 28;
|
|
;; November 11,25;
|
|
;; December 9,23
|
|
(ical:date-time-variant dtstart :month 10 :day 28 :zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 11 :day 11 :zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 11 :day 25 :zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 12 :day 23 :zone ict:est :dst nil)
|
|
;; (1998 9:00 AM EST) January 6,20;
|
|
;; February 3, 17
|
|
(ical:date-time-variant dtstart :year 1998 :month 1 :day 6
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :year 1998 :month 1 :day 20
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :year 1998 :month 2 :day 3
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :year 1998 :month 2 :day 17
|
|
:zone ict:est :dst nil))
|
|
:source rfc5545-sec3.8.5.3/9)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=WEEKLY;UNTIL=19971007T000000Z;WKST=SU;BYDAY=TU,TH\n"
|
|
"Weekly on Tuesday and Thursday for five weeks, using UNTIL"
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 9 :day 2
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:high (ical:make-date-time :year 1997 :month 10 :day 8
|
|
:hour 0 :minute 0 :second 0 :zone 0)
|
|
:members
|
|
(list
|
|
;; ==> (1997 9:00 AM EDT) September 2,4,9,11,16,18,23,25,30;
|
|
;; October 2
|
|
dtstart
|
|
(ical:date-time-variant dtstart :day 4 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 9 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 11 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 16 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 18 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 23 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 25 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 30 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :month 10 :day 2 :tz 'preserve))
|
|
:source rfc5545-sec3.8.5.3/10)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=WEEKLY;COUNT=10;WKST=SU;BYDAY=TU,TH\n"
|
|
"Weekly on Tuesday and Thursday for five weeks, using COUNT"
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 9 :day 2
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:high (ical:make-date-time :year 1997 :month 10 :day 8
|
|
:hour 0 :minute 0 :second 0 :zone 0)
|
|
:members
|
|
(list
|
|
;; ==> (1997 9:00 AM EDT) September 2,4,9,11,16,18,23,25,30;
|
|
;; October 2
|
|
dtstart
|
|
(ical:date-time-variant dtstart :day 4 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 9 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 11 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 16 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 18 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 23 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 25 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 30 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :month 10 :day 2 :tz 'preserve))
|
|
:source rfc5545-sec3.8.5.3/11)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR\n"
|
|
"Every other week on Monday, Wednesday, and Friday until December 24,
|
|
1997, starting on Monday, September 1, 1997"
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 9 :day 1
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:members
|
|
(list
|
|
dtstart
|
|
;; ==> (1997 9:00 AM EDT) September 1,3,5,15,17,19,29;
|
|
;; October 1,3,13,15,17
|
|
(ical:date-time-variant dtstart :day 3 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 5 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 15 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 17 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 19 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 29 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :month 10 :day 1 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :month 10 :day 3 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :month 10 :day 13 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :month 10 :day 15 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :month 10 :day 17 :tz 'preserve)
|
|
;; (1997 9:00 AM EST) October 27,29,31;
|
|
;; November 10,12,14,24,26,28;
|
|
;; December 8,10,12,22
|
|
(ical:date-time-variant dtstart :month 10 :day 27 :zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 10 :day 29 :zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 10 :day 31 :zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 11 :day 10 :zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 11 :day 12 :zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 11 :day 14 :zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 11 :day 24 :zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 11 :day 26 :zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 11 :day 28 :zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 12 :day 8 :zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 12 :day 10 :zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 12 :day 12 :zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 12 :day 22 :zone ict:est :dst nil))
|
|
:nonmembers
|
|
(list
|
|
;; These match the rule, but are just past the UNTIL date:
|
|
(ical:date-time-variant dtstart :month 12 :day 24 :zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 12 :day 26 :zone ict:est :dst nil))
|
|
:source rfc5545-sec3.8.5.3/12)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=8;WKST=SU;BYDAY=TU,TH\n"
|
|
"Every other week on Tuesday and Thursday, for 8 occurrences"
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 9 :day 2
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:members
|
|
;; ==> (1997 9:00 AM EDT) September 2,4,16,18,30;
|
|
;; October 2,14,16
|
|
(list
|
|
dtstart
|
|
(ical:date-time-variant dtstart :day 4 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 16 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 18 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 30 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :month 10 :day 2 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :month 10 :day 14 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :month 10 :day 16 :tz 'preserve))
|
|
:source rfc5545-sec3.8.5.3/13)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=MONTHLY;COUNT=10;BYDAY=1FR\n"
|
|
"Monthly on the first Friday for 10 occurrences"
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 9 :day 5
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:members
|
|
(list
|
|
;; ==> (1997 9:00 AM EDT) September 5;October 3
|
|
dtstart
|
|
(ical:date-time-variant dtstart :month 10 :day 3 :tz 'preserve)
|
|
;; (1997 9:00 AM EST) November 7;December 5
|
|
(ical:date-time-variant dtstart :month 11 :day 7 :zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 12 :day 5 :zone ict:est :dst nil)
|
|
;; (1998 9:00 AM EST) January 2;February 6;March 6;April 3
|
|
(ical:date-time-variant dtstart :year 1998 :month 1 :day 2
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :year 1998 :month 2 :day 6
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :year 1998 :month 3 :day 6
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :year 1998 :month 4 :day 3
|
|
:zone ict:est :dst nil)
|
|
;; (1998 9:00 AM EDT) May 1;June 5
|
|
(ical:date-time-variant dtstart :year 1998 :month 5 :day 1 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :year 1998 :month 6 :day 5 :tz 'preserve))
|
|
:source rfc5545-sec3.8.5.3/14)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=MONTHLY;UNTIL=19971224T000000Z;BYDAY=1FR\n"
|
|
"Monthly on the first Friday until December 24, 1997"
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 9 :day 5
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:members
|
|
(list
|
|
;; ==> (1997 9:00 AM EDT) September 5; October 3
|
|
dtstart
|
|
(ical:date-time-variant dtstart :month 10 :day 3 :tz 'preserve)
|
|
;; (1997 9:00 AM EST) November 7;December 5
|
|
(ical:date-time-variant dtstart :month 11 :day 7 :zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 12 :day 5 :zone ict:est :dst nil))
|
|
:source rfc5545-sec3.8.5.3/15)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=1SU,-1SU\n"
|
|
"Every other month on the first and last Sunday of the month for 10 occurrences"
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 9 :day 7
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:members
|
|
(list
|
|
;; ==> (1997 9:00 AM EDT) September 7,28
|
|
dtstart
|
|
(ical:date-time-variant dtstart :day 28 :tz 'preserve)
|
|
;; (1997 9:00 AM EST) November 2,30
|
|
(ical:date-time-variant dtstart :month 11 :day 2 :zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 11 :day 30 :zone ict:est :dst nil)
|
|
;; (1998 9:00 AM EST) January 4,25;March 1,29
|
|
(ical:date-time-variant dtstart :year 1998 :month 1 :day 4
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :year 1998 :month 1 :day 25
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :year 1998 :month 3 :day 1
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :year 1998 :month 3 :day 29
|
|
:zone ict:est :dst nil)
|
|
;; (1998 9:00 AM EDT) May 3,31
|
|
(ical:date-time-variant dtstart :year 1998 :month 5 :day 3 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :year 1998 :month 5 :day 31 :tz 'preserve))
|
|
:source rfc5545-sec3.8.5.3/16)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=MONTHLY;COUNT=6;BYDAY=-2MO\n"
|
|
"Monthly on the second-to-last Monday of the month for 6 months"
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 9 :day 22
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:members
|
|
(list
|
|
;; ==> (1997 9:00 AM EDT) September 22;October 20
|
|
dtstart
|
|
(ical:date-time-variant dtstart :month 10 :day 20 :tz 'preserve)
|
|
;; (1997 9:00 AM EST) November 17;December 22
|
|
(ical:date-time-variant dtstart :month 11 :day 17
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 12 :day 22
|
|
:zone ict:est :dst nil)
|
|
;; (1998 9:00 AM EST) January 19;February 16
|
|
(ical:date-time-variant dtstart :year 1998 :month 1 :day 19
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :year 1998 :month 2 :day 16
|
|
:zone ict:est :dst nil))
|
|
:source rfc5545-sec3.8.5.3/17)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=MONTHLY;BYMONTHDAY=-3\n"
|
|
"Monthly on the third-to-the-last day of the month, forever"
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 9 :day 28
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:high (ical:make-date-time :year 1998 :month 3 :day 1
|
|
:hour 0 :minute 0 :second 0
|
|
:zone ict:est :dst nil)
|
|
:members
|
|
(list
|
|
;; ==> (1997 9:00 AM EDT) September 28
|
|
dtstart
|
|
;; (1997 9:00 AM EST) October 29;November 28;December 29
|
|
(ical:date-time-variant dtstart :month 10 :day 29
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 11 :day 28
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 12 :day 29
|
|
:zone ict:est :dst nil)
|
|
;; (1998 9:00 AM EST) January 29;February 26
|
|
(ical:date-time-variant dtstart :year 1998 :month 1 :day 29
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :year 1998 :month 2 :day 26
|
|
:zone ict:est :dst nil))
|
|
:source rfc5545-sec3.8.5.3/18)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=2,15\n"
|
|
"Monthly on the 2nd and 15th of the month for 10 occurrences"
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 9 :day 2
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:members
|
|
(list
|
|
;; ==> (1997 9:00 AM EDT) September 2,15;October 2,15
|
|
dtstart
|
|
(ical:date-time-variant dtstart :day 15 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :month 10 :day 2 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :month 10 :day 15 :tz 'preserve)
|
|
;; (1997 9:00 AM EST) November 2,15;December 2,15
|
|
(ical:date-time-variant dtstart :month 11 :day 2
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 11 :day 15
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 12 :day 2
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 12 :day 15
|
|
:zone ict:est :dst nil)
|
|
;; (1998 9:00 AM EST) January 2,15
|
|
(ical:date-time-variant dtstart :year 1998 :month 1 :day 2
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :year 1998 :month 1 :day 15
|
|
:zone ict:est :dst nil))
|
|
:source rfc5545-sec3.8.5.3/19)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=1,-1\n"
|
|
"Monthly on the first and last day of the month for 10 occurrences"
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 9 :day 30
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:members
|
|
(list
|
|
;; ==> (1997 9:00 AM EDT) September 30;October 1
|
|
dtstart
|
|
(ical:date-time-variant dtstart :month 10 :day 1 :tz 'preserve)
|
|
;; (1997 9:00 AM EST) October 31;November 1,30;December 1,31
|
|
(ical:date-time-variant dtstart :month 10 :day 31
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 11 :day 1
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 11 :day 30
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 12 :day 1
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 12 :day 31
|
|
:zone ict:est :dst nil)
|
|
;; (1998 9:00 AM EST) January 1,31;February 1
|
|
(ical:date-time-variant dtstart :year 1998 :month 1 :day 1
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :year 1998 :month 1 :day 31
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :year 1998 :month 2 :day 1
|
|
:zone ict:est :dst nil))
|
|
:source rfc5545-sec3.8.5.3/20)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=MONTHLY;INTERVAL=18;COUNT=10;BYMONTHDAY=10,11,12,13,14,15\n"
|
|
"Every 18 months on the 10th thru 15th of the month for 10 occurrences"
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 9 :day 10
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:members
|
|
(append
|
|
(list
|
|
;; ==> (1997 9:00 AM EDT) September 10,11,12,13,14,15
|
|
dtstart
|
|
(ical:date-time-variant dtstart :day 11 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 12 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 13 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 14 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 15 :tz 'preserve))
|
|
|
|
;; (1999 9:00 AM EST) March 10,11,12,13
|
|
(let ((mar99 (ical:make-date-time :year 1999 :month 3 :day 10
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:est :dst nil)))
|
|
(list
|
|
mar99
|
|
(ical:date-time-variant mar99 :day 11 :tz 'preserve)
|
|
(ical:date-time-variant mar99 :day 12 :tz 'preserve)
|
|
(ical:date-time-variant mar99 :day 13 :tz 'preserve))))
|
|
:nonmembers
|
|
(list
|
|
;; These match the rule but are excluded by the COUNT clause:
|
|
(ical:make-date-time :year 1999 :month 3 :day 14
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:est :dst nil)
|
|
(ical:make-date-time :year 1999 :month 3 :day 15
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:est :dst nil))
|
|
:source rfc5545-sec3.8.5.3/21)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=MONTHLY;INTERVAL=2;BYDAY=TU\n"
|
|
"Every Tuesday, every other month"
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 9 :day 2
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:high (ical:make-date-time :year 1998 :month 4 :day 1
|
|
:hour 0 :minute 0 :second 0
|
|
:zone ict:est :dst nil)
|
|
:members
|
|
(list
|
|
;; ==> (1997 9:00 AM EDT) September 2,9,16,23,30
|
|
dtstart
|
|
(ical:date-time-variant dtstart :day 9 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 16 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 23 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 30 :tz 'preserve)
|
|
;; (1997 9:00 AM EST) November 4,11,18,25
|
|
(ical:date-time-variant dtstart :month 11 :day 4
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 11 :day 11
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 11 :day 11
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 11 :day 25
|
|
:zone ict:est :dst nil)
|
|
;; (1998 9:00 AM EST) January 6,13,20,27;March 3,10,17,24,31
|
|
(ical:date-time-variant dtstart :year 1998 :month 1 :day 6
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :year 1998 :month 1 :day 13
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :year 1998 :month 1 :day 20
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :year 1998 :month 1 :day 27
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :year 1998 :month 3 :day 3
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :year 1998 :month 3 :day 10
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :year 1998 :month 3 :day 17
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :year 1998 :month 3 :day 24
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :year 1998 :month 3 :day 31
|
|
:zone ict:est :dst nil))
|
|
:nonmembers
|
|
;; e.g. Tuesdays in December 1997:
|
|
(list
|
|
(ical:date-time-variant dtstart :month 12 :day 2 :zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 12 :day 9 :zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 12 :day 16 :zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 12 :day 23 :zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 12 :day 30 :zone ict:est :dst nil))
|
|
:source rfc5545-sec3.8.5.3/22)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=YEARLY;COUNT=10;BYMONTH=6,7\n"
|
|
"Yearly in June and July for 10 occurrences"
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 6 :day 10
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
;; Note: Since none of the BYDAY, BYMONTHDAY, or BYYEARDAY
|
|
;; clauses are specified, the month day is gotten from "DTSTART"
|
|
:members
|
|
;; ==> (1997 9:00 AM EDT) June 10;July 10
|
|
;; (1998 9:00 AM EDT) June 10;July 10
|
|
;; (1999 9:00 AM EDT) June 10;July 10
|
|
;; (2000 9:00 AM EDT) June 10;July 10
|
|
;; (2001 9:00 AM EDT) June 10;July 10
|
|
(mapcan
|
|
(lambda (y)
|
|
(list
|
|
(ical:date-time-variant dtstart :year y :month 6 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :year y :month 7 :tz 'preserve)))
|
|
(number-sequence 1997 2001))
|
|
:source rfc5545-sec3.8.5.3/23)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=YEARLY;INTERVAL=2;COUNT=10;BYMONTH=1,2,3\n"
|
|
"Every other year on January, February, and March for 10 occurrences"
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 3 :day 10
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:est :dst nil)
|
|
:members
|
|
;; ==> (1997 9:00 AM EST) March 10
|
|
;; (1999 9:00 AM EST) January 10;February 10;March 10
|
|
;; (2001 9:00 AM EST) January 10;February 10;March 10
|
|
;; (2003 9:00 AM EST) January 10;February 10;March 10
|
|
(cons
|
|
dtstart
|
|
;; FIXME: this mapcan appears to produce a spurious warning:
|
|
(with-suppressed-warnings ((ignored-return-value mapcan))
|
|
(mapcan
|
|
(lambda (y)
|
|
(list
|
|
(ical:date-time-variant dtstart :year y :month 1 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :year y :month 2 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :year y :month 3 :tz 'preserve)))
|
|
(list 1999 2001 2003))))
|
|
:source rfc5545-sec3.8.5.3/24)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=YEARLY;INTERVAL=3;COUNT=10;BYYEARDAY=1,100,200\n"
|
|
"Every third year on the 1st, 100th, and 200th day for 10 occurrences"
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 1 :day 1
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:est :dst nil)
|
|
|
|
:members
|
|
(list
|
|
;; ==> (1997 9:00 AM EST) January 1
|
|
dtstart
|
|
;; (1997 9:00 AM EDT) April 10;July 19
|
|
(ical:date-time-variant dtstart :month 4 :day 10 :zone ict:edt :dst t)
|
|
(ical:date-time-variant dtstart :month 7 :day 19 :zone ict:edt :dst t)
|
|
;; (2000 9:00 AM EST) January 1
|
|
(ical:date-time-variant dtstart :year 2000 :tz 'preserve)
|
|
;; (2000 9:00 AM EDT) April 9;July 18
|
|
(ical:date-time-variant dtstart :year 2000 :month 4 :day 9 :zone ict:edt :dst t)
|
|
(ical:date-time-variant dtstart :year 2000 :month 7 :day 18 :zone ict:edt :dst t)
|
|
;; (2003 9:00 AM EST) January 1
|
|
(ical:date-time-variant dtstart :year 2003 :tz 'preserve)
|
|
;; (2003 9:00 AM EDT) April 10;July 19
|
|
(ical:date-time-variant dtstart :year 2003 :month 4 :day 10 :zone ict:edt :dst t)
|
|
(ical:date-time-variant dtstart :year 2003 :month 7 :day 19 :zone ict:edt :dst t)
|
|
;; (2006 9:00 AM EST) January 1
|
|
(ical:date-time-variant dtstart :year 2006 :tz 'preserve))
|
|
:source rfc5545-sec3.8.5.3/25)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=YEARLY;BYDAY=20MO\n"
|
|
"Every 20th Monday of the year, forever"
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 5 :day 19
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:members
|
|
(list
|
|
;; ==> (1997 9:00 AM EDT) May 19
|
|
;; (1998 9:00 AM EDT) May 18
|
|
;; (1999 9:00 AM EDT) May 17
|
|
;; ...
|
|
dtstart
|
|
(ical:date-time-variant dtstart :year 1998 :day 18 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :year 1999 :day 17 :tz 'preserve))
|
|
:source rfc5545-sec3.8.5.3/26)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=YEARLY;BYWEEKNO=20;BYDAY=MO\n"
|
|
"Every year on Monday in Week 20 (where the week starts Monday), forever"
|
|
:tz ict:tz-eastern
|
|
:dtstart
|
|
(ical:make-date-time :year 1997 :month 5 :day 12
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:members
|
|
(list
|
|
(ical:date-time-variant dtstart :year 1998 :day 11 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :year 1999 :day 17 :tz 'preserve))
|
|
:nonmembers
|
|
(list
|
|
(ical:date-time-variant dtstart :year 1998 :day 12 :tz 'preserve) ; a Tuesday
|
|
(ical:date-time-variant dtstart :year 1998 :day 18 :tz 'preserve)) ; wrong weekno
|
|
:source rfc5545-sec3.8.5.3/27)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=TH\n"
|
|
"Every Thursday in March, forever"
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 3 :day 13
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:est :dst nil)
|
|
:members
|
|
(append
|
|
;; ==> (1997 9:00 AM EST) March 13,20,27
|
|
(mapcar
|
|
(lambda (d)
|
|
(ical:date-time-variant dtstart :day d :tz 'preserve))
|
|
(list 13 20 27))
|
|
;; (1998 9:00 AM EST) March 5,12,19,26
|
|
(mapcar
|
|
(lambda (d)
|
|
(ical:date-time-variant dtstart :year 1998 :day d :tz 'preserve))
|
|
(list 5 12 19 26))
|
|
;; (1999 9:00 AM EST) March 4,11,18,25
|
|
(mapcar
|
|
(lambda (d)
|
|
(ical:date-time-variant dtstart :year 1999 :day d :tz 'preserve))
|
|
(list 4 11 18 25)))
|
|
:source rfc5545-sec3.8.5.3/28)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=YEARLY;BYDAY=TH;BYMONTH=6,7,8\n"
|
|
"Every Thursday, but only during June, July, and August, forever"
|
|
:tags '(:expensive-test)
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 6 :day 5
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:members
|
|
(append
|
|
;; ==> (1997 9:00 AM EDT) June 5,12,19,26;July 3,10,17,24,31;
|
|
;; August 7,14,21,28
|
|
(mapcar
|
|
(lambda (d)
|
|
(ical:date-time-variant dtstart :day d :tz 'preserve))
|
|
(list 5 12 19 26))
|
|
(mapcar
|
|
(lambda (d)
|
|
(ical:date-time-variant dtstart :month 7 :day d :tz 'preserve))
|
|
(list 3 10 17 24 31))
|
|
(mapcar
|
|
(lambda (d)
|
|
(ical:date-time-variant dtstart :month 8 :day d :tz 'preserve))
|
|
(list 7 14 21 28))
|
|
;; (1998 9:00 AM EDT) June 4,11,18,25;July 2,9,16,23,30;
|
|
;; August 6,13,20,27
|
|
(mapcar
|
|
(lambda (d)
|
|
(ical:date-time-variant dtstart :year 1998 :day d :tz 'preserve))
|
|
(list 4 11 18 25))
|
|
(mapcar
|
|
(lambda (d)
|
|
(ical:date-time-variant dtstart :year 1998 :month 7 :day d :tz 'preserve))
|
|
(list 2 9 16 23 30))
|
|
(mapcar
|
|
(lambda (d)
|
|
(ical:date-time-variant dtstart :year 1998 :month 8 :day d :tz 'preserve))
|
|
(list 6 13 20 27))
|
|
;; (1999 9:00 AM EDT) June 3,10,17,24;July 1,8,15,22,29;
|
|
;; August 5,12,19,26
|
|
(mapcar
|
|
(lambda (d)
|
|
(ical:date-time-variant dtstart :year 1999 :day d :tz 'preserve))
|
|
(list 3 10 17 24))
|
|
(mapcar
|
|
(lambda (d)
|
|
(ical:date-time-variant dtstart :year 1999 :month 7 :day d :tz 'preserve))
|
|
(list 1 8 15 22 29))
|
|
(mapcar
|
|
(lambda (d)
|
|
(ical:date-time-variant dtstart :year 1999 :month 8 :day d :tz 'preserve))
|
|
(list 5 12 19 26)))
|
|
:source rfc5545-sec3.8.5.3/29)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13\n"
|
|
"Every Friday the 13th, forever, *excluding* DTSTART "
|
|
:tags '(:expensive-test)
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 9 :day 2
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:high (ical:make-date-time :year 2000 :month 10 :day 14
|
|
:hour 0 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:exdates (list dtstart)
|
|
:members
|
|
(list
|
|
;; ==> (1998 9:00 AM EST) February 13;March 13;November 13
|
|
;; (1999 9:00 AM EDT) August 13
|
|
;; (2000 9:00 AM EDT) October 13
|
|
;; ...
|
|
(ical:date-time-variant dtstart :year 1998 :month 2 :day 13
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :year 1998 :month 3 :day 13
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :year 1998 :month 11 :day 13
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :year 1999 :month 8 :day 13 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :year 2000 :month 10 :day 13 :tz 'preserve))
|
|
:source rfc5545-sec3.8.5.3/30)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=MONTHLY;BYDAY=SA;BYMONTHDAY=7,8,9,10,11,12,13\n"
|
|
"The first Saturday that follows the first Sunday of the month, forever"
|
|
:tags '(:expensive-test)
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 9 :day 13
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:high (ical:make-date-time :year 1998 :month 6 :day 14
|
|
:hour 0 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:members
|
|
(list
|
|
;; ==> (1997 9:00 AM EDT) September 13;October 11
|
|
dtstart
|
|
(ical:date-time-variant dtstart :month 10 :day 11 :tz 'preserve)
|
|
;; (1997 9:00 AM EST) November 8;December 13
|
|
(ical:date-time-variant dtstart :month 11 :day 8 :zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 12 :day 13 :zone ict:est :dst nil)
|
|
;; (1998 9:00 AM EST) January 10;February 7;March 7
|
|
(ical:date-time-variant dtstart :year 1998 :month 1 :day 10
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :year 1998 :month 2 :day 7
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :year 1998 :month 3 :day 7
|
|
:zone ict:est :dst nil)
|
|
;; (1998 9:00 AM EDT) April 11;May 9;June 13...
|
|
(ical:date-time-variant dtstart :year 1998 :month 4 :day 11 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :year 1998 :month 5 :day 9 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :year 1998 :month 6 :day 13 :tz 'preserve))
|
|
:source rfc5545-sec3.8.5.3/31)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=YEARLY;INTERVAL=4;BYMONTH=11;BYDAY=TU;BYMONTHDAY=2,3,4,5,6,7,8\n"
|
|
"Every 4 years, the first Tuesday after a Monday in November, forever
|
|
(U.S. Presidential Election day)"
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1996 :month 11 :day 5
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:est :dst nil)
|
|
|
|
:members
|
|
(list
|
|
;; ==> (1996 9:00 AM EST) November 5
|
|
dtstart
|
|
;; (2000 9:00 AM EST) November 7
|
|
(ical:date-time-variant dtstart :year 2000 :day 7 :tz 'preserve)
|
|
;; (2004 9:00 AM EST) November 2
|
|
(ical:date-time-variant dtstart :year 2004 :day 2 :tz 'preserve))
|
|
:source rfc5545-sec3.8.5.3/32)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=3\n"
|
|
"The third instance into the month of one of Tuesday, Wednesday, or
|
|
Thursday, for the next 3 months"
|
|
:tags '(:expensive-test)
|
|
;; TODO: Yikes, why is this so slow??
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 9 :day 4
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
|
|
:members
|
|
(list
|
|
;; ==> (1997 9:00 AM EDT) September 4;October 7
|
|
;; (1997 9:00 AM EST) November 6
|
|
dtstart
|
|
(ical:date-time-variant dtstart :month 10 :day 7 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :month 11 :day 6 :zone ict:est :dst nil))
|
|
:source rfc5545-sec3.8.5.3/33)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-2\n"
|
|
"The second-to-last weekday of the month"
|
|
:tags '(:expensive-test)
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 9 :day 29
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:high (ical:make-date-time :year 1998 :month 4 :day 1
|
|
:hour 0 :minute 0 :second 0
|
|
:zone ict:est :dst nil)
|
|
:members
|
|
(list
|
|
;; ==> (1997 9:00 AM EDT) September 29
|
|
dtstart
|
|
;; (1997 9:00 AM EST) October 30;November 27;December 30
|
|
(ical:date-time-variant dtstart :month 10 :day 30 :zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 11 :day 27 :zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :month 12 :day 30 :zone ict:est :dst nil)
|
|
;; (1998 9:00 AM EST) January 29;February 26;March 30
|
|
(ical:date-time-variant dtstart :year 1998 :month 1 :day 29
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :year 1998 :month 2 :day 26
|
|
:zone ict:est :dst nil)
|
|
(ical:date-time-variant dtstart :year 1998 :month 3 :day 30
|
|
:zone ict:est :dst nil))
|
|
:source rfc5545-sec3.8.5.3/34)
|
|
|
|
(ict:rrule-test
|
|
;; corrected, see Errata ID 3883: https://www.rfc-editor.org/errata/eid3883
|
|
"RRULE:FREQ=HOURLY;INTERVAL=3;UNTIL=19970902T210000Z\n"
|
|
"Every 3 hours from 9:00 AM to 5:00 PM on a specific day"
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 9 :day 2
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
|
|
:members
|
|
(list
|
|
;; ==> (September 2, 1997 EDT) 09:00,12:00,15:00
|
|
dtstart
|
|
(ical:date-time-variant dtstart :hour 12 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :hour 15 :tz 'preserve))
|
|
:source rfc5545-sec3.8.5.3/35)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=MINUTELY;INTERVAL=15;COUNT=6\n"
|
|
"Every 15 minutes for 6 occurrences"
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 9 :day 2
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
|
|
:members
|
|
(list
|
|
;; ==> (September 2, 1997 EDT) 09:00,09:15,09:30,09:45,10:00,10:15
|
|
dtstart
|
|
(ical:date-time-variant dtstart :minute 15 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :minute 30 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :minute 45 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :hour 10 :minute 0 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :hour 10 :minute 15 :tz 'preserve))
|
|
:source rfc5545-sec3.8.5.3/36)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=MINUTELY;INTERVAL=90;COUNT=4\n"
|
|
"Every hour and a half for 4 occurrences"
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 9 :day 2
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:members
|
|
(list
|
|
;; ==> (September 2, 1997 EDT) 09:00,10:30;12:00;13:30
|
|
dtstart
|
|
(ical:date-time-variant dtstart :hour 10 :minute 30 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :hour 12 :minute 0 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :hour 13 :minute 30 :tz 'preserve))
|
|
:source rfc5545-sec3.8.5.3/37)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=DAILY;BYHOUR=9,10,11,12,13,14,15,16;BYMINUTE=0,20,40\n"
|
|
"Every 20 minutes from 9:00 AM to 4:40 PM every day"
|
|
:tags '(:expensive-test)
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 9 :day 2
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:high (ical:make-date-time :year 1997 :month 9 :day 4
|
|
:hour 0 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:members
|
|
(append
|
|
;; ==> (September 2, 1997 EDT) 9:00,9:20,9:40,10:00,10:20,
|
|
;; ... 16:00,16:20,16:40
|
|
(mapcan
|
|
(lambda (h)
|
|
(list
|
|
(ical:date-time-variant dtstart :hour h :minute 0 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :hour h :minute 20 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :hour h :minute 40 :tz 'preserve)))
|
|
(number-sequence 9 16))
|
|
;; (September 3, 1997 EDT) 9:00,9:20,9:40,10:00,10:20,
|
|
;; ...16:00,16:20,16:40
|
|
(mapcan
|
|
(lambda (h)
|
|
(list
|
|
(ical:date-time-variant dtstart :hour h :day 3 :minute 0 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :hour h :day 3 :minute 20 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :hour h :day 3 :minute 40 :tz 'preserve)))
|
|
(number-sequence 9 16)))
|
|
:source rfc5545-sec3.8.5.3/38)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=MINUTELY;INTERVAL=20;BYHOUR=9,10,11,12,13,14,15,16\n"
|
|
"Every 20 minutes from 9:00 AM to 4:40 PM every day
|
|
(Alternative rule for the previous example)"
|
|
:tags '(:expensive-test)
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 9 :day 2
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:high (ical:make-date-time :year 1997 :month 9 :day 4
|
|
:hour 0 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:members
|
|
(append
|
|
;; ==> (September 2, 1997 EDT) 9:00,9:20,9:40,10:00,10:20,
|
|
;; ... 16:00,16:20,16:40
|
|
(mapcan
|
|
(lambda (h)
|
|
(list
|
|
(ical:date-time-variant dtstart :hour h :minute 0 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :hour h :minute 20 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :hour h :minute 40 :tz 'preserve)))
|
|
(number-sequence 9 16))
|
|
;; (September 3, 1997 EDT) 9:00,9:20,9:40,10:00,10:20,
|
|
;; ...16:00,16:20,16:40
|
|
(mapcan
|
|
(lambda (h)
|
|
(list
|
|
(ical:date-time-variant dtstart :hour h :day 3 :minute 0 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :hour h :day 3 :minute 20 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :hour h :day 3 :minute 40 :tz 'preserve)))
|
|
(number-sequence 9 16)))
|
|
:source rfc5545-sec3.8.5.3/39)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=MO\n"
|
|
"An example where the days generated makes a difference because of WKST:
|
|
every other week on Tuesday and Sunday, week start Monday, for four recurrences"
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 8 :day 5
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:members
|
|
(list
|
|
;; ==> (1997 EDT) August 5,10,19,24
|
|
dtstart
|
|
(ical:date-time-variant dtstart :day 10 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 19 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 24 :tz 'preserve))
|
|
:source rfc5545-sec3.8.5.3/40)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=SU\n"
|
|
"An example where the days generated makes a difference because of WKST:
|
|
every other week on Tuesday and Sunday, week start Sunday, for four recurrences"
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 1997 :month 8 :day 5
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:members
|
|
(list
|
|
;; ==> (1997 EDT) August 5,17,19,31
|
|
dtstart
|
|
(ical:date-time-variant dtstart :day 17 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 19 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :day 31 :tz 'preserve))
|
|
:source rfc5545-sec3.8.5.3/41)
|
|
|
|
(ict:rrule-test
|
|
"RRULE:FREQ=MONTHLY;BYMONTHDAY=15,30;COUNT=5\n"
|
|
"An example where an invalid date (i.e., February 30) is ignored."
|
|
:tz ict:tz-eastern
|
|
:dtstart (ical:make-date-time :year 2007 :month 1 :day 15
|
|
:hour 9 :minute 0 :second 0
|
|
:zone ict:est :dst nil)
|
|
:high (ical:make-date-time :year 2007 :month 4 :day 1
|
|
:hour 0 :minute 0 :second 0
|
|
:zone ict:edt :dst t)
|
|
:members
|
|
(list
|
|
;; ==> (2007 EST) January 15,30
|
|
;; (2007 EST) February 15
|
|
;; (2007 EDT) March 15,30
|
|
dtstart
|
|
(ical:date-time-variant dtstart :day 30 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :month 2 :day 15 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :month 3 :day 15 :zone ict:edt :dst t)
|
|
(ical:date-time-variant dtstart :month 3 :day 30 :zone ict:edt :dst t))
|
|
:nonmembers
|
|
(list
|
|
(ical:date-time-variant dtstart :month 2 :day 28 :tz 'preserve)
|
|
(ical:date-time-variant dtstart :month 2 :day 30 :tz 'preserve))
|
|
:source rfc5545-sec3.8.5.3/42)
|
|
|
|
;; Local Variables:
|
|
;; read-symbol-shorthands: (("ict:" . "icalendar-test-") ("icr:" . "icalendar-recur-") ("ical:" . "icalendar-"))
|
|
;; End:
|
|
;;; icalendar-recur-tests.el ends here
|