;; lib/events/tests/timezone.sx — timezones + DST. (define ev-tz-pass 0) (define ev-tz-fail 0) (define ev-tz-failures (list)) (define ev-tz-check! (fn (name got expected) (if (= got expected) (set! ev-tz-pass (+ ev-tz-pass 1)) (do (set! ev-tz-fail (+ ev-tz-fail 1)) (append! ev-tz-failures (str name "\n expected: " expected "\n got: " got)))))) ;; Wall-clock (civil + minute-of-day) an occurrence's UTC start maps to in a tz. (define ev-tz-local-of (fn (tz utc-dt) (let ((l (ev-tz-utc->local tz utc-dt))) (list (ev-dt->civil l) (ev-dt-tod l))))) (define ev-tz-run-all! (fn () (do (let ((nyc (ev-tz-fixed "EST" -300))) (do (ev-tz-check! "fixed zone: utc -> local subtracts 5h" (ev-tz-utc->local nyc (ev-dt 2026 1 1 17 0)) (ev-dt 2026 1 1 12 0)) (ev-tz-check! "fixed zone: local -> utc adds 5h back" (ev-tz-local->utc nyc (ev-dt 2026 1 1 12 0)) (ev-dt 2026 1 1 17 0)) (ev-tz-check! "UTC zone is identity" (ev-tz-local->utc ev-tz-utc (ev-dt 2026 6 1 9 0)) (ev-dt 2026 6 1 9 0)))) (ev-tz-check! "London winter offset is 0 (GMT)" (ev-tz-offset ev-tz-london (ev-dt 2026 1 15 12 0)) 0) (ev-tz-check! "London summer offset is 60 (BST)" (ev-tz-offset ev-tz-london (ev-dt 2026 7 15 12 0)) 60) (ev-tz-check! "Paris winter offset is 60 (CET)" (ev-tz-offset ev-tz-paris (ev-dt 2026 1 15 12 0)) 60) (ev-tz-check! "Paris summer offset is 120 (CEST)" (ev-tz-offset ev-tz-paris (ev-dt 2026 7 15 12 0)) 120) (ev-tz-check! "DST starts last Sunday of March" (ev-dt->civil (ev-tz-transition 2026 (ev-tz-rule 3 -1 6 60))) (list 2026 3 29)) (ev-tz-check! "DST ends last Sunday of October" (ev-dt->civil (ev-tz-transition 2026 (ev-tz-rule 10 -1 6 60))) (list 2026 10 25)) (ev-tz-check! "09:00 London in winter is 09:00 UTC" (ev-tz-local->utc ev-tz-london (ev-dt 2026 1 15 9 0)) (ev-dt 2026 1 15 9 0)) (ev-tz-check! "09:00 London in summer is 08:00 UTC" (ev-tz-local->utc ev-tz-london (ev-dt 2026 7 15 9 0)) (ev-dt 2026 7 15 8 0)) (ev-tz-check! "round trip utc -> local -> utc" (ev-tz-local->utc ev-tz-london (ev-tz-utc->local ev-tz-london (ev-dt 2026 7 15 8 0))) (ev-dt 2026 7 15 8 0)) (let ((ev (ev-event-tz (quote standup) (ev-dt 2026 3 27 9 0) 60 {:freq :daily :count 5} 10 ev-tz-london))) (let ((occs (ev-expand ev (ev-date 2026 3 1) (ev-date 2026 4 5)))) (do (ev-tz-check! "daily occurrences shift in UTC across the DST boundary" (map (fn (o) (ev-dt-tod (get o :start))) occs) (list 540 540 480 480 480)) (ev-tz-check! "but every occurrence stays 09:00 local wall-clock" (map (fn (o) (first (rest (ev-tz-local-of ev-tz-london (get o :start))))) occs) (list 540 540 540 540 540)) (ev-tz-check! "occurrence dates are stable in local time" (map (fn (o) (ev-civ-d (first (ev-tz-local-of ev-tz-london (get o :start))))) occs) (list 27 28 29 30 31))))) (let ((wk (ev-event-tz (quote class) (ev-dt 2026 3 23 18 0) 90 {:freq :weekly :byday (list 0)} 5 ev-tz-london))) (let ((occs (ev-expand wk (ev-date 2026 3 1) (ev-date 2026 4 20)))) (ev-tz-check! "weekly Monday 18:00 London stays 18:00 local each week" (map (fn (o) (first (rest (ev-tz-local-of ev-tz-london (get o :start))))) occs) (list 1080 1080 1080 1080)))) (let ((plain (ev-event (quote p) (ev-dt 2026 3 27 9 0) 60 {:freq :daily :count 3} 1))) (ev-tz-check! "plain event expands naively (no UTC shift)" (map (fn (o) (ev-dt-tod (get o :start))) (ev-expand plain (ev-date 2026 3 1) (ev-date 2026 4 5))) (list 540 540 540)))))) (define ev-timezone-tests-run! (fn () (do (set! ev-tz-pass 0) (set! ev-tz-fail 0) (set! ev-tz-failures (list)) (ev-tz-run-all!) {:failures ev-tz-failures :total (+ ev-tz-pass ev-tz-fail) :passed ev-tz-pass :failed ev-tz-fail})))