oficina / calendar Public

Keep recurrence walking past DST gap occurrences

Co-Authored-By: Codex <codex@openai.com>
Paul Campbell pushed by paul@keymail.dev 1a6dcd0753568e63b9cafeaaf2b75d1e807d1926 parent b0ef4fa
2 files changed, +13 −1
  • internal/calendar/occurrences.go +1 −1
  • internal/calendar/recurrence_horizon_test.go +12 −0

CI passed — run details

diff --git a/internal/calendar/occurrences.go b/internal/calendar/occurrences.go
index af85413..27014a4 100644
--- a/internal/calendar/occurrences.go
+++ b/internal/calendar/occurrences.go
@@ -98,7 +98,7 @@ func ExpandComplete(startLocal, endLocal, tz string, allDay bool, r Rule, exdate
}
occStart := cur
occEnd, endOK := shiftDays(cur, dayspan, end)
- if !endOK || occEnd.Before(occStart) {
+ if !endOK {
break
}
diff --git a/internal/calendar/recurrence_horizon_test.go b/internal/calendar/recurrence_horizon_test.go
index 2e43266..9ff0f73 100644
--- a/internal/calendar/recurrence_horizon_test.go
+++ b/internal/calendar/recurrence_horizon_test.go
@@ -93,3 +93,15 @@ func TestInvalidInputsCannotBecomeCompleteAtCountOrWindow(t *testing.T) {
t.Fatal("reversed window complete")
}
}
+
+func TestGapOccurrenceDoesNotTruncateLaterWindows(t *testing.T) {
+ r, _ := ParseRule("FREQ=DAILY")
+ from := time.Date(2026, 4, 1, 0, 0, 0, 0, time.UTC).Unix()
+ got, complete := ExpandComplete("2026-03-01T01:30", "2026-03-01T02:00", "Europe/Dublin", false, r, "", from, from+86400)
+ if len(got) != 1 || !complete {
+ t.Fatalf("April window after spring gap: got=%v complete=%v", got, complete)
+ }
+ if got[0].Local != "2026-04-01T01:30" || got[0].EndUTC-got[0].StartUTC != 1800 {
+ t.Fatalf("April occurrence=%+v", got[0])
+ }
+}