package ical import ( "io" "strings" "time" "unicode/utf8" "usoscal/internal/store" ) const ( stamp = "20060102T150405Z" lineLimit = 75 ) func Summary(event store.Event) string { if event.Kind == "" || event.Kind == event.Title { return event.Title } return event.Title + " (" + event.Kind + ")" } func Location(event store.Event) string { place := event.Room switch { case place != "" && event.Building != "": place += " (" + event.Building + ")" case place == "": place = event.Building } return joinNonEmpty(", ", place, event.Address) } func Description(event store.Event) string { about := event.Kind if event.Group != "" { about = joinNonEmpty(", ", about, "group "+event.Group) } lecturers := "" if event.Lecturers != "" { lecturers = "Lecturer: " + event.Lecturers } return joinNonEmpty("\n", about, lecturers, event.URL) } func Write(w io.Writer, name, host string, events []store.Event) error { var b strings.Builder line(&b, "BEGIN:VCALENDAR") line(&b, "VERSION:2.0") line(&b, "PRODID:-//usoscal//EN") line(&b, "CALSCALE:GREGORIAN") line(&b, "METHOD:PUBLISH") line(&b, "X-WR-CALNAME:"+escape(name)) line(&b, "X-WR-TIMEZONE:Europe/Warsaw") line(&b, "REFRESH-INTERVAL;VALUE=DURATION:PT6H") line(&b, "X-PUBLISHED-TTL:PT6H") for _, event := range events { line(&b, "BEGIN:VEVENT") line(&b, "UID:"+event.UID+"@"+host) line(&b, "DTSTAMP:"+utc(event.UpdatedAt)) line(&b, "LAST-MODIFIED:"+utc(event.UpdatedAt)) line(&b, "DTSTART:"+utc(event.Start)) line(&b, "DTEND:"+utc(event.End)) line(&b, "SUMMARY:"+escape(Summary(event))) if location := Location(event); location != "" { line(&b, "LOCATION:"+escape(location)) } if description := Description(event); description != "" { line(&b, "DESCRIPTION:"+escape(description)) } if event.URL != "" { line(&b, "URL:"+event.URL) } line(&b, "END:VEVENT") } line(&b, "END:VCALENDAR") _, err := io.WriteString(w, b.String()) return err } func utc(t time.Time) string { return t.UTC().Format(stamp) } func joinNonEmpty(separator string, parts ...string) string { kept := parts[:0:0] for _, part := range parts { if part != "" { kept = append(kept, part) } } return strings.Join(kept, separator) } var escaper = strings.NewReplacer(`\`, `\\`, ";", `\;`, ",", `\,`, "\r\n", `\n`, "\n", `\n`, "\r", "") func escape(text string) string { return escaper.Replace(text) } func line(b *strings.Builder, content string) { for len(content) > lineLimit { cut := lineLimit for cut > 0 && !utf8.RuneStart(content[cut]) { cut-- } b.WriteString(content[:cut]) b.WriteString("\r\n ") content = content[cut:] } b.WriteString(content) b.WriteString("\r\n") }