blob: 19a916b00757e91ee3810e80b5802b92eea3549a [file] [log] [blame]
Serge Bazanski8ef457f2021-07-11 14:42:38 +00001package calendar
2
3import (
4 "fmt"
5 "sync"
6 "time"
7
8 "github.com/golang/glog"
9)
10
11// UpcomingEvent is a calendar event that will happen in the near future, or is
12// currently happening (relative to same arbitrary timestamp of 'now',
13// depending on the way the UpcomingEvent is crated).
14//
15// It is a best-effort parse of an ICS/iCal event into some event that can be
16// interpreted as a 'community event', to be displayed publicly on a site.
17type UpcomingEvent struct {
18 // UID is the unique ICS/iCal ID of this event.
19 UID string
20 // Summary is the 'title' of the event, usually a short one-liner.
21 Summary string
22 // Start and End of the events, potentially whole-day dates. See EventTime
23 // for more information.
24 // If Start is WholeDay then so is End, and vice-versa.
25 Start *EventTime
26 // End of the event, exclusive of the time range (ie. if a timestamp it
27 // defines the timestamp at which the next event can start; if it's whole
28 // day it defines the first day on which the event does not take place).
29 End *EventTime
30 // Tentative is whether this event is marked as 'Tentative' in the source
31 // calendar.
32 Tentative bool
33}
34
35// WholeDay returns true if this is a whole-day (or multi-day) event.
36func (u *UpcomingEvent) WholeDay() bool {
37 return u.Start.WholeDay
38}
39
40var (
41 // onceComplainWarsawGone gates throwing a very verbose message about being
42 // unable to localize UpcomingEvents into Warsaw local time by WarsawDate.
43 onceComplainWarsawGone sync.Once
44)
45
46// WarsawDate prints a human-readable timestamp that makes sense within the
47// context of this event taking place in Warsaw, or at least in the same
48// timezone as Warsaw.
49// It will return a time in one of the following formats:
50//
51// YEAR/MONTH/DAY
52// (For one-day events)
53//
54// YEAR/MONTH/DAY - DAY
55// (For multi-day events within the same month)
56//
57// YEAR/MONTH/DAY - YEAR/MONTH/DAY
58// (For multi-day events spanning more than one month)
59//
60// YEAR/MONTH/DAY HH:MM - HH:MM
61// (For timestamped events within the same day)
62//
63// YEAR/MONTH/DAY HH:MM - YEAR/MONTH/DAY HH:MM
64// (For timestamped events spanning more than one day)
65//
66func (u *UpcomingEvent) WarsawDate() string {
67 YM := "2006/01"
68 D := "02"
69 YMD := "2006/01/02"
70 HM := "15:04"
71 YMDHM := "2006/01/02 15:04"
72
73 if u.WholeDay() {
74 start := u.Start.Time
75 // ICS whole-day dates are [start, end), ie. 'end' is exclusive.
76 end := u.End.Time.AddDate(0, 0, -1)
77 if start == end {
78 // Event is one-day.
79 return start.Format(YMD)
80 }
81 if start.Year() == end.Year() && start.Month() == end.Month() {
82 // Event starts and ends on the same month, print shortened form.
83 return fmt.Sprintf("%s/%s - %s", start.Format(YM), start.Format(D), end.Format(D))
84 }
85 // Event spans multiple months, print full form.
86 return fmt.Sprintf("%s - %s", start.Format(YMD), end.Format(YMD))
87 }
88
89 warsaw, err := time.LoadLocation("Europe/Warsaw")
90 if err != nil {
91 onceComplainWarsawGone.Do(func() {
92 glog.Errorf("Could not load Europe/Warsaw timezone, did the city cease to exist? LoadLoaction: %v", err)
93 })
94 // Even in the face of a cataclysm, degrade gracefully and assume the
95 // users are local to this service's timezone.
96 warsaw = time.Local
97 }
98
99 start := u.Start.Time.In(warsaw)
100 end := u.End.Time.In(warsaw)
101 if start.Year() == end.Year() && start.Month() == end.Month() && start.Day() == end.Day() {
102 // Event starts and ends on same day, print shortened form.
103 return fmt.Sprintf("%s %s - %s", start.Format(YMD), start.Format(HM), end.Format(HM))
104 }
105 // Event spans multiple days, print full form.
106 return fmt.Sprintf("%s - %s", start.Format(YMDHM), end.Format(YMDHM))
107}
108
109func (u *UpcomingEvent) String() string {
110 return fmt.Sprintf("%s (%s)", u.Summary, u.WarsawDate())
111}
112
113func (e *UpcomingEvent) Elapsed(t time.Time) bool {
114 // Event hasn't started yet?
115 if e.Start.Time.After(t) {
116 return false
117 }
118 // Event has started, but hasn't ended?
119 if e.End.Time.After(t) {
120 return false
121 }
122 return true
123}