events: MONTHLY RRULE expansion (bymonthday + ordinal byday) + 13 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 44s

BYMONTHDAY (negative = from end), ordinal BYDAY ({:ord :wd}, last-weekday),
default day-of-month skipping short months. Weekly+monthly share ev-emit-occs.
37/37 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-06 23:52:39 +00:00
parent 797c5f9147
commit 70aea21601
5 changed files with 354 additions and 61 deletions

View File

@@ -73,6 +73,22 @@
(ev-dt->civil
(ev-dt 2026 12 25 8 0))
(list 2026 12 25))
(ev-cal-check!
"days in feb 2024 (leap)"
(ev-days-in-month 2024 2)
29)
(ev-cal-check!
"days in feb 2026"
(ev-days-in-month 2026 2)
28)
(ev-cal-check!
"add months wraps year"
(ev-add-months 2026 11 3)
(list 2027 2))
(ev-cal-check!
"add months within year"
(ev-add-months 2026 1 5)
(list 2026 6))
(let
((ev (ev-event (quote one) (ev-dt 2026 6 10 14 0) 60 nil 1)))
(do
@@ -264,6 +280,124 @@
(list 2026 6 5)
(list 2026 6 8)
(list 2026 6 10))))
(let
((md (ev-event (quote md) (ev-dt 2026 1 15 9 0) 60 {:bymonthday (list 15) :freq :monthly} 1)))
(do
(ev-cal-check!
"monthly bymonthday 15th"
(ev-cal-starts
(ev-expand
md
(ev-date 2026 1 1)
(ev-date 2026 4 1)))
(list
(list 2026 1 15)
(list 2026 2 15)
(list 2026 3 15)))
(ev-cal-check!
"monthly preserves time of day"
(ev-dt-tod
(get
(first
(ev-expand
md
(ev-date 2026 1 1)
(ev-date 2026 4 1)))
:start))
540)))
(let
((mm (ev-event (quote mm) (ev-dt 2026 1 1 9 0) 60 {:bymonthday (list 1 15) :freq :monthly :count 4} 1)))
(ev-cal-check!
"monthly multiple bymonthday sorted within month"
(ev-cal-starts
(ev-expand
mm
(ev-date 2026 1 1)
(ev-date 2026 12 1)))
(list
(list 2026 1 1)
(list 2026 1 15)
(list 2026 2 1)
(list 2026 2 15))))
(let
((ml (ev-event (quote ml) (ev-dt 2026 1 31 9 0) 60 {:bymonthday (list -1) :freq :monthly} 1)))
(ev-cal-check!
"monthly bymonthday -1 is last day"
(ev-cal-starts
(ev-expand
ml
(ev-date 2026 1 1)
(ev-date 2026 4 1)))
(list
(list 2026 1 31)
(list 2026 2 28)
(list 2026 3 31))))
(let
((mn (ev-event (quote mn) (ev-dt 2026 1 1 9 0) 60 {:freq :monthly :byday (list {:ord 2 :wd 1})} 1)))
(ev-cal-check!
"monthly 2nd tuesday"
(ev-cal-shape
(ev-expand
mn
(ev-date 2026 1 1)
(ev-date 2026 4 1)))
(list
(list (list 2026 1 13) 1)
(list (list 2026 2 10) 1)
(list (list 2026 3 10) 1))))
(let
((mz (ev-event (quote mz) (ev-dt 2026 1 1 9 0) 60 {:freq :monthly :byday (list {:ord -1 :wd 4})} 1)))
(ev-cal-check!
"monthly last friday"
(ev-cal-shape
(ev-expand
mz
(ev-date 2026 1 1)
(ev-date 2026 4 1)))
(list
(list (list 2026 1 30) 4)
(list (list 2026 2 27) 4)
(list (list 2026 3 27) 4))))
(let
((m31 (ev-event (quote m31) (ev-dt 2026 1 31 9 0) 60 {:freq :monthly :count 4} 1)))
(ev-cal-check!
"monthly default day-of-month skips short months"
(ev-cal-starts
(ev-expand
m31
(ev-date 2026 1 1)
(ev-date 2026 12 1)))
(list
(list 2026 1 31)
(list 2026 3 31)
(list 2026 5 31)
(list 2026 7 31))))
(let
((mi (ev-event (quote mi) (ev-dt 2026 1 10 9 0) 60 {:interval 3 :freq :monthly :count 3} 1)))
(ev-cal-check!
"monthly interval 3 steps by quarter"
(ev-cal-starts
(ev-expand
mi
(ev-date 2026 1 1)
(ev-date 2027 1 1)))
(list
(list 2026 1 10)
(list 2026 4 10)
(list 2026 7 10))))
(let
((mc (ev-event (quote mc) (ev-dt 2026 1 5 9 0) 60 {:freq :monthly :count 12} 1)))
(ev-cal-check!
"monthly count window-independent (clip middle)"
(ev-cal-starts
(ev-expand
mc
(ev-date 2026 4 1)
(ev-date 2026 6 30)))
(list
(list 2026 4 5)
(list 2026 5 5)
(list 2026 6 5))))
(let
((a (ev-event (quote a) (ev-dt 2026 6 2 10 0) 30 {:freq :daily :count 2} 1))
(b