Compare commits
4 Commits
loops/blog
...
loops/even
| Author | SHA1 | Date | |
|---|---|---|---|
| 7d1d732623 | |||
| b257813926 | |||
| 6716af69dc | |||
| 78b45a331e |
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"lang": "events",
|
"lang": "events",
|
||||||
"total_passed": 376,
|
"total_passed": 391,
|
||||||
"total_failed": 0,
|
"total_failed": 0,
|
||||||
"total": 376,
|
"total": 391,
|
||||||
"suites": [
|
"suites": [
|
||||||
{"name":"calendar","passed":51,"failed":0,"total":51},
|
{"name":"calendar","passed":51,"failed":0,"total":51},
|
||||||
{"name":"timezone","passed":17,"failed":0,"total":17},
|
{"name":"timezone","passed":25,"failed":0,"total":25},
|
||||||
{"name":"ical","passed":56,"failed":0,"total":56},
|
{"name":"ical","passed":63,"failed":0,"total":63},
|
||||||
{"name":"availability","passed":22,"failed":0,"total":22},
|
{"name":"availability","passed":22,"failed":0,"total":22},
|
||||||
{"name":"api","passed":41,"failed":0,"total":41},
|
{"name":"api","passed":41,"failed":0,"total":41},
|
||||||
{"name":"booking","passed":82,"failed":0,"total":82},
|
{"name":"booking","passed":82,"failed":0,"total":82},
|
||||||
@@ -17,5 +17,5 @@
|
|||||||
{"name":"federation","passed":29,"failed":0,"total":29},
|
{"name":"federation","passed":29,"failed":0,"total":29},
|
||||||
{"name":"integration","passed":8,"failed":0,"total":8}
|
{"name":"integration","passed":8,"failed":0,"total":8}
|
||||||
],
|
],
|
||||||
"generated": "2026-06-07T20:02:48+00:00"
|
"generated": "2026-06-10T22:03:34+00:00"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
# events scoreboard
|
# events scoreboard
|
||||||
|
|
||||||
**376 / 376 passing** (0 failure(s)).
|
**391 / 391 passing** (0 failure(s)).
|
||||||
|
|
||||||
| Suite | Passed | Total | Status |
|
| Suite | Passed | Total | Status |
|
||||||
|-------|--------|-------|--------|
|
|-------|--------|-------|--------|
|
||||||
| calendar | 51 | 51 | ok |
|
| calendar | 51 | 51 | ok |
|
||||||
| timezone | 17 | 17 | ok |
|
| timezone | 25 | 25 | ok |
|
||||||
| ical | 56 | 56 | ok |
|
| ical | 63 | 63 | ok |
|
||||||
| availability | 22 | 22 | ok |
|
| availability | 22 | 22 | ok |
|
||||||
| api | 41 | 41 | ok |
|
| api | 41 | 41 | ok |
|
||||||
| booking | 82 | 82 | ok |
|
| booking | 82 | 82 | ok |
|
||||||
|
|||||||
@@ -335,6 +335,17 @@
|
|||||||
(do
|
(do
|
||||||
(ev-ic-check! "Paris DAYLIGHT goes to +0200 (CEST)" (ev-ic-find vtz "TZOFFSETTO:+0200") "TZOFFSETTO:+0200")
|
(ev-ic-check! "Paris DAYLIGHT goes to +0200 (CEST)" (ev-ic-find vtz "TZOFFSETTO:+0200") "TZOFFSETTO:+0200")
|
||||||
(ev-ic-check! "Paris STANDARD goes to +0100 (CET)" (ev-ic-find vtz "TZOFFSETTO:+0100") "TZOFFSETTO:+0100")))
|
(ev-ic-check! "Paris STANDARD goes to +0100 (CET)" (ev-ic-find vtz "TZOFFSETTO:+0100") "TZOFFSETTO:+0100")))
|
||||||
|
;; southern hemisphere exports a valid VTIMEZONE too: reversed offsets,
|
||||||
|
;; first-Sunday rules, and the -480 rule time folds back to local 02:00/03:00
|
||||||
|
(let
|
||||||
|
((vtz (ev-ical-vtimezone ev-tz-sydney)))
|
||||||
|
(do
|
||||||
|
(ev-ic-check! "Sydney VTIMEZONE names the zone" (ev-ic-find vtz "TZID") "TZID:Australia/Sydney")
|
||||||
|
(ev-ic-check! "Sydney DAYLIGHT goes to +1100 (AEDT)" (ev-ic-find vtz "TZOFFSETTO:+1100") "TZOFFSETTO:+1100")
|
||||||
|
(ev-ic-check! "Sydney STANDARD goes to +1000 (AEST)" (ev-ic-find vtz "TZOFFSETTO:+1000") "TZOFFSETTO:+1000")
|
||||||
|
(ev-ic-check! "Sydney DAYLIGHT rule is first Sunday of October" (ev-ic-find vtz "RRULE:FREQ=YEARLY;BYMONTH=10") "RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU")
|
||||||
|
(ev-ic-check! "Sydney STANDARD rule is first Sunday of April" (ev-ic-find vtz "RRULE:FREQ=YEARLY;BYMONTH=4") "RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU")
|
||||||
|
(ev-ic-check! "Sydney DAYLIGHT begins 02:00 local (AEST std, -480 folded)" (ev-ic-find vtz "DTSTART") "DTSTART:19701004T020000")))
|
||||||
|
|
||||||
;; ---- VCALENDAR carries one VTIMEZONE per distinct zone ----
|
;; ---- VCALENDAR carries one VTIMEZONE per distinct zone ----
|
||||||
(let
|
(let
|
||||||
@@ -371,7 +382,13 @@
|
|||||||
(get
|
(get
|
||||||
(ev/ical-lines->event (ev/event->ical-lines (ev-event-tz (quote a) (ev-dt 2026 7 15 18 0) 60 nil 1 ev-tz-london)))
|
(ev/ical-lines->event (ev/event->ical-lines (ev-event-tz (quote a) (ev-dt 2026 7 15 18 0) 60 nil 1 ev-tz-london)))
|
||||||
:dtstart)
|
:dtstart)
|
||||||
(ev-dt 2026 7 15 18 0)))))
|
(ev-dt 2026 7 15 18 0))
|
||||||
|
(ev-ic-check!
|
||||||
|
"import parses a southern-zone DTSTART;TZID local time"
|
||||||
|
(get
|
||||||
|
(ev/ical-lines->event (ev/event->ical-lines (ev-event-tz (quote a) (ev-dt 2026 1 15 18 0) 60 nil 1 ev-tz-sydney)))
|
||||||
|
:dtstart)
|
||||||
|
(ev-dt 2026 1 15 18 0)))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
ev-ical-tests-run!
|
ev-ical-tests-run!
|
||||||
|
|||||||
@@ -76,6 +76,58 @@
|
|||||||
ev-tz-paris
|
ev-tz-paris
|
||||||
(ev-dt 2026 7 15 12 0))
|
(ev-dt 2026 7 15 12 0))
|
||||||
120)
|
120)
|
||||||
|
;; ---- southern hemisphere (reversed seasons) ----
|
||||||
|
(ev-tz-check!
|
||||||
|
"Sydney January offset is 660 (AEDT, summer DST)"
|
||||||
|
(ev-tz-offset
|
||||||
|
ev-tz-sydney
|
||||||
|
(ev-dt 2026 1 15 12 0))
|
||||||
|
660)
|
||||||
|
(ev-tz-check!
|
||||||
|
"Sydney July offset is 600 (AEST, winter std)"
|
||||||
|
(ev-tz-offset
|
||||||
|
ev-tz-sydney
|
||||||
|
(ev-dt 2026 7 15 12 0))
|
||||||
|
600)
|
||||||
|
(ev-tz-check!
|
||||||
|
"Sydney DST starts first Sunday of October"
|
||||||
|
(ev-dt->civil
|
||||||
|
(+ (ev-tz-transition 2026 (get ev-tz-sydney :dst-start)) 480))
|
||||||
|
(list 2026 10 4))
|
||||||
|
(ev-tz-check!
|
||||||
|
"Sydney DST ends first Sunday of April"
|
||||||
|
(ev-dt->civil
|
||||||
|
(+ (ev-tz-transition 2026 (get ev-tz-sydney :dst-end)) 480))
|
||||||
|
(list 2026 4 5))
|
||||||
|
(ev-tz-check!
|
||||||
|
"09:00 Sydney in summer (AEDT) is previous-day 22:00 UTC"
|
||||||
|
(ev-tz-local->utc
|
||||||
|
ev-tz-sydney
|
||||||
|
(ev-dt 2026 1 15 9 0))
|
||||||
|
(ev-dt 2026 1 14 22 0))
|
||||||
|
(ev-tz-check!
|
||||||
|
"09:00 Sydney in winter (AEST) is previous-day 23:00 UTC"
|
||||||
|
(ev-tz-local->utc
|
||||||
|
ev-tz-sydney
|
||||||
|
(ev-dt 2026 7 15 9 0))
|
||||||
|
(ev-dt 2026 7 14 23 0))
|
||||||
|
(let
|
||||||
|
((au (ev-event-tz (quote au) (ev-dt 2026 4 3 9 0) 60 {:freq :daily :count 5} 8 ev-tz-sydney)))
|
||||||
|
(let
|
||||||
|
((occs (ev-expand au (ev-date 2026 3 25) (ev-date 2026 4 12))))
|
||||||
|
(do
|
||||||
|
(ev-tz-check!
|
||||||
|
"Sydney daily occurrences shift in UTC across the autumn DST end"
|
||||||
|
(map (fn (o) (ev-dt-tod (get o :start))) occs)
|
||||||
|
(list 1320 1320 1380 1380 1380))
|
||||||
|
(ev-tz-check!
|
||||||
|
"but every Sydney occurrence stays 09:00 local wall-clock"
|
||||||
|
(map
|
||||||
|
(fn
|
||||||
|
(o)
|
||||||
|
(first (rest (ev-tz-local-of ev-tz-sydney (get o :start)))))
|
||||||
|
occs)
|
||||||
|
(list 540 540 540 540 540)))))
|
||||||
(ev-tz-check!
|
(ev-tz-check!
|
||||||
"DST starts last Sunday of March"
|
"DST starts last Sunday of March"
|
||||||
(ev-dt->civil
|
(ev-dt->civil
|
||||||
|
|||||||
@@ -13,8 +13,11 @@
|
|||||||
;; :fixed — a constant offset.
|
;; :fixed — a constant offset.
|
||||||
;; :dst — std/dst offsets + two transition rules. Transitions are given in
|
;; :dst — std/dst offsets + two transition rules. Transitions are given in
|
||||||
;; UTC (EU zones all switch at 01:00 UTC), so the offset at any UTC
|
;; UTC (EU zones all switch at 01:00 UTC), so the offset at any UTC
|
||||||
;; instant is a direct range check; no recursion. Northern-hemisphere
|
;; instant is a direct range check; no recursion. Both hemispheres
|
||||||
;; ordering (dst-start < dst-end within a year) is assumed.
|
;; are supported: northern zones have dst-start < dst-end (DST is the
|
||||||
|
;; interval [start, end)); southern zones have dst-start > dst-end
|
||||||
|
;; (DST wraps the year boundary), detected by comparing the two
|
||||||
|
;; transitions — see ev-tz-offset.
|
||||||
;;
|
;;
|
||||||
;; Requires calendar.sx (ev-dt, ev-days-from-civil, ev-civil-from-days,
|
;; Requires calendar.sx (ev-dt, ev-days-from-civil, ev-civil-from-days,
|
||||||
;; ev-civ-y, ev-floor-div, ev-resolve-nth-weekday).
|
;; ev-civ-y, ev-floor-div, ev-resolve-nth-weekday).
|
||||||
@@ -58,10 +61,20 @@
|
|||||||
(let
|
(let
|
||||||
((start (ev-tz-transition year (get tz :dst-start)))
|
((start (ev-tz-transition year (get tz :dst-start)))
|
||||||
(end (ev-tz-transition year (get tz :dst-end))))
|
(end (ev-tz-transition year (get tz :dst-end))))
|
||||||
|
;; Northern hemisphere: dst-start < dst-end, DST is the closed-open
|
||||||
|
;; interval [start, end). Southern hemisphere: dst-start > dst-end
|
||||||
|
;; (DST begins in spring ~Oct and ends ~Apr), so within a calendar
|
||||||
|
;; year DST wraps the boundary — active OUTSIDE [end, start).
|
||||||
(if
|
(if
|
||||||
(and (>= utc-dt start) (< utc-dt end))
|
(< start end)
|
||||||
(get tz :dst-offset)
|
(if
|
||||||
(get tz :std-offset)))))
|
(and (>= utc-dt start) (< utc-dt end))
|
||||||
|
(get tz :dst-offset)
|
||||||
|
(get tz :std-offset))
|
||||||
|
(if
|
||||||
|
(or (>= utc-dt start) (< utc-dt end))
|
||||||
|
(get tz :dst-offset)
|
||||||
|
(get tz :std-offset))))))
|
||||||
(else 0))))
|
(else 0))))
|
||||||
|
|
||||||
;; UTC instant -> local wall-clock.
|
;; UTC instant -> local wall-clock.
|
||||||
@@ -98,6 +111,19 @@
|
|||||||
120
|
120
|
||||||
(ev-tz-rule 3 -1 6 60)
|
(ev-tz-rule 3 -1 6 60)
|
||||||
(ev-tz-rule 10 -1 6 60)))
|
(ev-tz-rule 10 -1 6 60)))
|
||||||
|
;; Southern hemisphere: AEST +600 (std, winter), AEDT +660 (dst, summer). DST
|
||||||
|
;; begins 02:00 AEST first Sunday October and ends 03:00 AEDT first Sunday April
|
||||||
|
;; — both 16:00 UTC the preceding Saturday, i.e. -480 minutes from the Sunday in
|
||||||
|
;; the rule (the model adds rule :time to the resolved weekday's UTC midnight).
|
||||||
|
;; dst-start (Oct) > dst-end (Apr), so ev-tz-offset takes the wrap-the-year path.
|
||||||
|
(define
|
||||||
|
ev-tz-sydney
|
||||||
|
(ev-tz-dst
|
||||||
|
"Australia/Sydney"
|
||||||
|
600
|
||||||
|
660
|
||||||
|
(ev-tz-rule 10 1 6 -480)
|
||||||
|
(ev-tz-rule 4 1 6 -480)))
|
||||||
|
|
||||||
;; ---- tz-aware event expansion ----
|
;; ---- tz-aware event expansion ----
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ capacity rules, transactional booking, and a flow-driven notification dispatcher
|
|||||||
|
|
||||||
## Status (rolling)
|
## Status (rolling)
|
||||||
|
|
||||||
`bash lib/events/conformance.sh` → **376/376** (Phases 1-4 + 13 ext + tz iCal export via TZID + VTIMEZONE)
|
`bash lib/events/conformance.sh` → **391/391** (Phases 1-4 + 14 ext + tz iCal export via TZID + VTIMEZONE + southern-hemisphere DST incl. iCal round-trip)
|
||||||
|
|
||||||
## Ground rules
|
## Ground rules
|
||||||
|
|
||||||
@@ -88,6 +88,26 @@ lib/events/api.sx ── (events/schedule) (events/book) (events/agenda) ──
|
|||||||
|
|
||||||
## Progress log
|
## Progress log
|
||||||
|
|
||||||
|
- 2026-06-10 — Harden southern-hemisphere DST: explicit iCal coverage for the
|
||||||
|
previous commit's unverified claim that "southern zones round-trip through
|
||||||
|
iCal unchanged". Added a Sydney VTIMEZONE export block (TZID:Australia/Sydney,
|
||||||
|
DAYLIGHT→+1100/STANDARD→+1000, first-Sunday rules BYMONTH=10/4 BYDAY=1SU, and
|
||||||
|
DAYLIGHT DTSTART:19701004T020000 — proving the −480 rule time folds back to
|
||||||
|
local 02:00 AEST) and a southern-zone DTSTART;TZID export→import round-trip.
|
||||||
|
+7 tests (ical 63). 391/391 green.
|
||||||
|
- 2026-06-10 — Southern-hemisphere DST. The `:dst` zone model assumed northern
|
||||||
|
ordering (dst-start < dst-end, DST = [start, end)); southern zones (DST begins
|
||||||
|
~Oct, ends ~Apr) have dst-start > dst-end and so silently never entered DST —
|
||||||
|
`ev-tz-offset` returned std year-round. Fixed by detecting the ordering: when
|
||||||
|
start < end DST is the interval [start, end); when start > end DST wraps the
|
||||||
|
year boundary (active when `utc ≥ start OR utc < end`). Added predefined
|
||||||
|
`ev-tz-sydney` (AEST +600 / AEDT +660; transitions 02:00 AEST first-Sun-Oct
|
||||||
|
and 03:00 AEDT first-Sun-Apr, both 16:00 UTC the prior Saturday → rule time
|
||||||
|
−480). VTIMEZONE export already rule-agnostic, so southern zones round-trip
|
||||||
|
too (the −480 folds the from-offset back to the correct local 02:00/03:00).
|
||||||
|
+8 tests (timezone 25): summer/winter offsets, both transition dates,
|
||||||
|
local→utc both seasons, and a daily expansion crossing the autumn DST-end that
|
||||||
|
shifts in UTC (1320·1320·1380·1380·1380) yet stays 09:00 local. 384/384 green.
|
||||||
- 2026-06-07 — VTIMEZONE iCal export (supersedes the UTC-Z tz fix — full DST
|
- 2026-06-07 — VTIMEZONE iCal export (supersedes the UTC-Z tz fix — full DST
|
||||||
fidelity). A tz event now exports DTSTART;TZID=<name>:<local> (+ EXDATE/RDATE
|
fidelity). A tz event now exports DTSTART;TZID=<name>:<local> (+ EXDATE/RDATE
|
||||||
in the same TZID-local form; UNTIL stays UTC per RFC), and the VCALENDAR emits
|
in the same TZID-local form; UNTIL stays UTC per RFC), and the VCALENDAR emits
|
||||||
|
|||||||
Reference in New Issue
Block a user