package calendar import ( "strings" "time" ics "github.com/arran4/golang-ical" ) // CalendarClass is a class that represents what "class" the specified event is // // Currently, I have only observed this value as being `PUBLIC`, but other // values are handled as panics type CalendarClass byte const ( ClassPublic CalendarClass = iota ) func ParseClass(raw string) CalendarClass { switch raw { case "PUBLIC": return ClassPublic } panic("Unknown class: " + raw) } func (cc CalendarClass) String() string { switch cc { case ClassPublic: return "PUBLIC" default: panic("Unknown class") } } // CalendarEvent represents a single event in a calendar that is tied to a certain date type CalendarEvent struct { UID string // A summary of the event, including the name of the event // This value is set every time as far as I can tell Summary string // A fescription of the event, including more specific information. Not always // set, so don't rely on it Description string // Access class? of the event Class CalendarClass LastModified time.Time DateStamp time.Time StartTime time.Time EndTime time.Time // Every event comes with a category. This usually contains the name of the // class, along with some other information (Semester code, CRN, sections) Category struct { ClassName string OtherData string } } const calendarTimeFormat = "20060102T150405Z" // FromVEvent parses an ICS VEvent's format and outputs a specific calendar event, or an error func FromVEvent(external *ics.VEvent) (event CalendarEvent, err error) { if len(external.Components) > 0 { panic("Event has unhandled components") } for _, prop := range external.Properties { if len(prop.ICalParameters) > 0 { panic("Property has unhandled ICalParameters") } switch prop.IANAToken { case "UID": event.UID = prop.Value case "SUMMARY": event.Summary = prop.Value case "DESCRIPTION": event.Description = prop.Value case "CLASS": event.Class = ParseClass(prop.Value) case "LAST-MODIFIED": event.LastModified, err = time.Parse(calendarTimeFormat, prop.Value) if err != nil { return event, err } case "DTSTAMP": event.DateStamp, err = time.Parse(calendarTimeFormat, prop.Value) if err != nil { return event, err } case "DTSTART": event.StartTime, err = time.Parse(calendarTimeFormat, prop.Value) if err != nil { return event, err } case "DTEND": event.EndTime, err = time.Parse(calendarTimeFormat, prop.Value) if err != nil { return event, err } case "CATEGORIES": dashIndex := strings.Index(prop.Value, "-") // If there was no dash found, treat the property as its entire name if dashIndex == -1 { event.Category.ClassName = prop.Value } else { event.Category.ClassName = prop.Value[:dashIndex] event.Category.OtherData = prop.Value[dashIndex:] } default: panic("Unknown token when parsing event: " + prop.IANAToken) } } return event, nil }