blob: 141e4a63ae599c1793c1fa0675ba49edf7c42144 [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
Serge Bazanski717aad42021-07-11 16:03:43 +000022 // Full description of event. Might contain multiple lines of test.
23 Description string
Serge Bazanski8ef457f2021-07-11 14:42:38 +000024 // Start and End of the events, potentially whole-day dates. See EventTime
25 // for more information.
26 // If Start is WholeDay then so is End, and vice-versa.
27 Start *EventTime
28 // End of the event, exclusive of the time range (ie. if a timestamp it
29 // defines the timestamp at which the next event can start; if it's whole
30 // day it defines the first day on which the event does not take place).
31 End *EventTime
32 // Tentative is whether this event is marked as 'Tentative' in the source
33 // calendar.
34 Tentative bool
35}
36
37// WholeDay returns true if this is a whole-day (or multi-day) event.
38func (u *UpcomingEvent) WholeDay() bool {
39 return u.Start.WholeDay
40}
41
42var (
43 // onceComplainWarsawGone gates throwing a very verbose message about being
44 // unable to localize UpcomingEvents into Warsaw local time by WarsawDate.
45 onceComplainWarsawGone sync.Once
46)
47
48// WarsawDate prints a human-readable timestamp that makes sense within the
49// context of this event taking place in Warsaw, or at least in the same
50// timezone as Warsaw.
51// It will return a time in one of the following formats:
52//
53// YEAR/MONTH/DAY
54// (For one-day events)
55//
56// YEAR/MONTH/DAY - DAY
57// (For multi-day events within the same month)
58//
59// YEAR/MONTH/DAY - YEAR/MONTH/DAY
60// (For multi-day events spanning more than one month)
61//
62// YEAR/MONTH/DAY HH:MM - HH:MM
63// (For timestamped events within the same day)
64//
65// YEAR/MONTH/DAY HH:MM - YEAR/MONTH/DAY HH:MM
66// (For timestamped events spanning more than one day)
67//
68func (u *UpcomingEvent) WarsawDate() string {
69 YM := "2006/01"
70 D := "02"
71 YMD := "2006/01/02"
72 HM := "15:04"
73 YMDHM := "2006/01/02 15:04"
74
75 if u.WholeDay() {
76 start := u.Start.Time
77 // ICS whole-day dates are [start, end), ie. 'end' is exclusive.
78 end := u.End.Time.AddDate(0, 0, -1)
79 if start == end {
80 // Event is one-day.
81 return start.Format(YMD)
82 }
83 if start.Year() == end.Year() && start.Month() == end.Month() {
84 // Event starts and ends on the same month, print shortened form.
85 return fmt.Sprintf("%s/%s - %s", start.Format(YM), start.Format(D), end.Format(D))
86 }
87 // Event spans multiple months, print full form.
88 return fmt.Sprintf("%s - %s", start.Format(YMD), end.Format(YMD))
89 }
90
91 warsaw, err := time.LoadLocation("Europe/Warsaw")
92 if err != nil {
93 onceComplainWarsawGone.Do(func() {
94 glog.Errorf("Could not load Europe/Warsaw timezone, did the city cease to exist? LoadLoaction: %v", err)
95 })
96 // Even in the face of a cataclysm, degrade gracefully and assume the
97 // users are local to this service's timezone.
98 warsaw = time.Local
99 }
100
101 start := u.Start.Time.In(warsaw)
102 end := u.End.Time.In(warsaw)
103 if start.Year() == end.Year() && start.Month() == end.Month() && start.Day() == end.Day() {
104 // Event starts and ends on same day, print shortened form.
105 return fmt.Sprintf("%s %s - %s", start.Format(YMD), start.Format(HM), end.Format(HM))
106 }
107 // Event spans multiple days, print full form.
108 return fmt.Sprintf("%s - %s", start.Format(YMDHM), end.Format(YMDHM))
109}
110
111func (u *UpcomingEvent) String() string {
112 return fmt.Sprintf("%s (%s)", u.Summary, u.WarsawDate())
113}
114
115func (e *UpcomingEvent) Elapsed(t time.Time) bool {
116 // Event hasn't started yet?
117 if e.Start.Time.After(t) {
118 return false
119 }
120 // Event has started, but hasn't ended?
121 if e.End.Time.After(t) {
122 return false
123 }
124 return true
125}