| 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 |
| } |