blob: 141e4a63ae599c1793c1fa0675ba49edf7c42144 [file] [log] [blame]
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
}