package calendar import ( "fmt" "sync" "time" "github.com/golang/glog" ) // UpcomingEvent is a calendar event that will happen in the near future, or is // currently happening (relative to same arbitrary timestamp of 'now', // depending on the way the UpcomingEvent is crated). // // It is a best-effort parse of an ICS/iCal event into some event that can be // interpreted as a 'community event', to be displayed publicly on a site. type UpcomingEvent struct { // UID is the unique ICS/iCal ID of this event. UID string // Summary is the 'title' of the event, usually a short one-liner. Summary string // Full description of event. Might contain multiple lines of test. Description string // Start and End of the events, potentially whole-day dates. See EventTime // for more information. // If Start is WholeDay then so is End, and vice-versa. Start *EventTime // End of the event, exclusive of the time range (ie. if a timestamp it // defines the timestamp at which the next event can start; if it's whole // day it defines the first day on which the event does not take place). End *EventTime // Tentative is whether this event is marked as 'Tentative' in the source // calendar. Tentative bool } // WholeDay returns true if this is a whole-day (or multi-day) event. func (u *UpcomingEvent) WholeDay() bool { return u.Start.WholeDay } var ( // onceComplainWarsawGone gates throwing a very verbose message about being // unable to localize UpcomingEvents into Warsaw local time by WarsawDate. onceComplainWarsawGone sync.Once ) // WarsawDate prints a human-readable timestamp that makes sense within the // context of this event taking place in Warsaw, or at least in the same // timezone as Warsaw. // It will return a time in one of the following formats: // // YEAR/MONTH/DAY // (For one-day events) // // YEAR/MONTH/DAY - DAY // (For multi-day events within the same month) // // YEAR/MONTH/DAY - YEAR/MONTH/DAY // (For multi-day events spanning more than one month) // // YEAR/MONTH/DAY HH:MM - HH:MM // (For timestamped events within the same day) // // YEAR/MONTH/DAY HH:MM - YEAR/MONTH/DAY HH:MM // (For timestamped events spanning more than one day) // func (u *UpcomingEvent) WarsawDate() string { YM := "2006/01" D := "02" YMD := "2006/01/02" HM := "15:04" YMDHM := "2006/01/02 15:04" if u.WholeDay() { start := u.Start.Time // ICS whole-day dates are [start, end), ie. 'end' is exclusive. end := u.End.Time.AddDate(0, 0, -1) if start == end { // Event is one-day. return start.Format(YMD) } if start.Year() == end.Year() && start.Month() == end.Month() { // Event starts and ends on the same month, print shortened form. return fmt.Sprintf("%s/%s - %s", start.Format(YM), start.Format(D), end.Format(D)) } // Event spans multiple months, print full form. return fmt.Sprintf("%s - %s", start.Format(YMD), end.Format(YMD)) } warsaw, err := time.LoadLocation("Europe/Warsaw") if err != nil { onceComplainWarsawGone.Do(func() { glog.Errorf("Could not load Europe/Warsaw timezone, did the city cease to exist? LoadLoaction: %v", err) }) // Even in the face of a cataclysm, degrade gracefully and assume the // users are local to this service's timezone. warsaw = time.Local } start := u.Start.Time.In(warsaw) end := u.End.Time.In(warsaw) if start.Year() == end.Year() && start.Month() == end.Month() && start.Day() == end.Day() { // Event starts and ends on same day, print shortened form. return fmt.Sprintf("%s %s - %s", start.Format(YMD), start.Format(HM), end.Format(HM)) } // Event spans multiple days, print full form. return fmt.Sprintf("%s - %s", start.Format(YMDHM), end.Format(YMDHM)) } func (u *UpcomingEvent) String() string { return fmt.Sprintf("%s (%s)", u.Summary, u.WarsawDate()) } func (e *UpcomingEvent) Elapsed(t time.Time) bool { // Event hasn't started yet? if e.Start.Time.After(t) { return false } // Event has started, but hasn't ended? if e.End.Time.After(t) { return false } return true }