Free / Busy
Given a set of events, compute when someone is busy — and the gaps when they're free. Recurrences are expanded automatically.
from := time.Date(2026, 1, 5, 8, 0, 0, 0, time.UTC)
to := from.Add(10 * time.Hour) // a working day
busy, free := icalendar.FreeBusy(events, from, to)
for _, p := range free {
fmt.Printf("free %s–%s\n", p.Start.Format("15:04"), p.End.Format("15:04"))
}
How it works
BusyPeriods(events, from, to) []Period— every event's occurrences, clipped to the window and merged into a sorted, non-overlapping list. CANCELLED events are skipped; all-day events contribute their full span; an event that starts before the window but runs into it is still counted.FreeBusy(events, from, to) (busy, free []Period)— the busy list plus the free gaps that fill the rest of[from, to).
A Period is a half-open interval:
type Period struct {
Start, End time.Time
}
func (p Period) Duration() time.Duration
func (p Period) Contains(t time.Time) bool
Finding a free slot
busy, free := icalendar.FreeBusy(events, from, to)
_ = busy
for _, gap := range free {
if gap.Duration() >= 30*time.Minute {
fmt.Println("can meet at", gap.Start)
break
}
}