package calendar import ( "fmt" "time" ics "github.com/arran4/golang-ical" ) // EventTime is a timestamp for calendar events. It either represents a real // point-in time or a calender day, if it's a whole-day event. type EventTime struct { // Time is a timestamp in the timezone originally defined for this event if // WholeDay is true. Otherwise, it's a UTC time from which a year, month // and day can be extracted and treated as the indication of a 'calendar // day' in an unknown timezone. Time time.Time // WholeDay is true if this EventTime represents an entire calendar day. WholeDay bool } func (e *EventTime) String() string { if e.WholeDay { return fmt.Sprintf("%s (whole day)", e.Time.Format("2006/01/02")) } else { return e.Time.String() } } // parseICSTime attempts to parse a given ICS DT{START,END} object into an // EventTime, trying to figure out if the given object represents a timestamp // or a whole-day event. func parseICSTime(p *ics.IANAProperty) (*EventTime, error) { // If this is has a VALUE of DATE, then this is a whole-day time. // Otherwise, it's an actual timestamp. valueList, ok := p.ICalParameters[string(ics.ParameterValue)] if ok { if len(valueList) != 1 || valueList[0] != "DATE" { return nil, fmt.Errorf("unsupported time type: %v", valueList) } ts, err := time.Parse("20060102", p.Value) if err != nil { return nil, fmt.Errorf("could not parse date %q: %w", p.Value, err) } return &EventTime{ Time: ts, WholeDay: true, }, nil } // You would expect that nextcloud would emit VALUE == DATE-TIME for // timestamps, but that just doesn't seem to be the case. Maye I should // read the ICS standard... tzidList, ok := p.ICalParameters[string(ics.ParameterTzid)] if !ok || len(tzidList) != 1 { return nil, fmt.Errorf("TZID missing") } tzid := tzidList[0] location, err := time.LoadLocation(tzid) if err != nil { return nil, fmt.Errorf("could not parse TZID %q: %w", tzid, err) } ts, err := time.ParseInLocation("20060102T150405", p.Value, location) if err != nil { return nil, fmt.Errorf("could not parse time %q: %w", p.Value, err) } return &EventTime{ Time: ts, WholeDay: false, }, nil }