Skip to content

Commit 58d9898

Browse files
pantierragueldenstone
authored andcommitted
Restructured code a bit
1 parent 297a378 commit 58d9898

File tree

4 files changed

+151
-116
lines changed

4 files changed

+151
-116
lines changed

cmd/calendar-bot/calendar-bot.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ func main() {
9696
infoLog.Printf("Scheduling notifications for %s", notifyTime.Format("15:04"))
9797
s.Every(1).Day().At(notifyTime).Do(func() {
9898
infoLog.Println("Start Notification")
99-
cal, err := calendar.NewCalendar(conf.Calendar, infoLog)
99+
cal, err := calendar.ImportCalendar(conf.Calendar, infoLog)
100100
if err != nil {
101101
errLog.Printf("Could not read calendar info from %s\n", conf.Calendar)
102102
}

internal/message/message.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ type TemplatedMessage struct {
2929
txtTemplate *template.Template
3030
}
3131

32-
func NewTemplatedMessage(htmlTemplate, txtTemplate string, events []calendar.EventData, tz *time.Location) (TemplatedMessage, error) {
32+
func NewTemplatedMessage(htmlTemplate, txtTemplate string, events []calendar.Event, tz *time.Location) (TemplatedMessage, error) {
3333
msg := TemplatedMessage{}
3434
for _, evt := range events {
3535
event := Event{

pkg/calendar/calendar.go

+134-99
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,16 @@ import (
88
"time"
99

1010
"github.com/emersion/go-ical"
11-
// "github.com/teambition/rrule-go"
1211
)
1312

14-
type Calendar struct {
15-
url string
16-
tz *time.Location
17-
*ical.Calendar
18-
*log.Logger
13+
type IcalData struct {
14+
url string
15+
tz *time.Location
16+
parsed *ical.Calendar
17+
logger *log.Logger
1918
}
2019

21-
type EventData struct {
20+
type Event struct {
2221
UID string
2322
Start time.Time
2423
End time.Time
@@ -27,169 +26,205 @@ type EventData struct {
2726
Description string
2827
}
2928

30-
func NewCalendar(url string, l *log.Logger) (Calendar, error) {
31-
calendar := Calendar{url: url, tz: time.Local}
29+
// Obtains an iCal from a given URL
30+
func ImportCalendar(url string, l *log.Logger) (IcalData, error) {
31+
data := IcalData{url: url, tz: time.Local, logger: l}
3232

33+
// Download the ICS file
3334
resp, err := http.Get(url)
3435
if err != nil {
35-
return calendar, err
36+
return data, err
3637
}
3738
defer resp.Body.Close()
3839

40+
// Parse the ICS file
3941
parser := ical.NewDecoder(resp.Body)
40-
41-
cal, err := parser.Decode()
42+
parsedData, err := parser.Decode()
4243
if err != nil {
43-
return calendar, err
44+
return data, err
4445
}
45-
calendar.Calendar = cal
46-
calendar.Logger = l
47-
return calendar, nil
46+
47+
data.parsed = parsedData
48+
return data, nil
4849
}
4950

50-
func (cal Calendar) GetEventsOn(date time.Time) ([]EventData, error) {
51-
all_events := make(map[string]ical.Event)
52-
selected_events := make([]ical.Event, 0)
51+
// Assembles a list of events on a given date
52+
func (data IcalData) GetEventsOn(date time.Time) ([]Event, error) {
53+
54+
allEvents := make(map[string]ical.Event)
55+
selectedEvents := make([]Event, 0)
56+
57+
// Prepare map of all events
58+
for _, event := range data.parsed.Events() {
5359

54-
todayStart := GetDateWithoutTime(date)
55-
todayEnd := todayStart.Add(24 * time.Hour)
56-
for _, event := range cal.Events() {
5760
uid := event.Props.Get(ical.PropUID).Value
61+
allEvents[uid] = data.handleDuplicates(uid, event, allEvents)
5862

59-
// check for doubles via uid
60-
if _, ok := all_events[uid]; ok {
63+
}
6164

62-
createdProp := all_events[uid].Props.Get(ical.PropCreated)
63-
if createdProp == nil {
64-
continue
65-
}
66-
created_existing, err := createdProp.DateTime(cal.tz)
67-
if err != nil {
68-
continue
69-
}
65+
for _, event := range allEvents {
7066

71-
createdNewProp := event.Props.Get(ical.PropCreated)
72-
if createdNewProp == nil {
73-
continue
74-
}
67+
// Checks whether event happens on the given date
68+
if data.isSingleEventOnDate(event, date) ||
69+
data.isRecurringEventOnDate(event, date) {
7570

76-
created_new, err := createdNewProp.DateTime(cal.tz)
71+
convertedEvent, err := data.convertIcal(event, date)
7772
if err != nil {
78-
continue
79-
}
80-
81-
if created_existing.After(created_new) {
82-
continue
73+
return nil, err
8374
}
75+
selectedEvents = append(selectedEvents, convertedEvent)
8476
}
85-
all_events[event.Props.Get(ical.PropUID).Value] = event
8677
}
8778

88-
for _, event := range all_events {
89-
start, err := event.DateTimeStart(cal.tz)
90-
if err != nil {
91-
return []EventData{}, err
79+
sortEvents(selectedEvents)
80+
81+
return selectedEvents, nil
82+
}
83+
84+
// Checks whether the event is a single, standard event on the given date
85+
func (data IcalData) isSingleEventOnDate(event ical.Event, date time.Time) bool {
86+
87+
start, err := event.DateTimeStart(data.tz)
88+
if err != nil {
89+
data.logger.Printf("could not get start time: %s\n", err)
90+
return false
91+
}
92+
93+
end, err := event.DateTimeEnd(data.tz)
94+
if err != nil {
95+
data.logger.Printf("could not get end time: %s\n", err)
96+
return false
97+
}
98+
99+
todayStart := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, data.tz)
100+
todayEnd := todayStart.Add(24 * time.Hour)
101+
102+
isRegularEvent := (start.After(todayStart) || start.Equal(todayStart)) && start.Before(todayEnd)
103+
isSpanningEvent := start.Before(todayStart) && end.After(todayEnd)
104+
105+
return isRegularEvent || isSpanningEvent
106+
}
107+
108+
// Checks whether an recurring event happens on a given date
109+
func (data IcalData) isRecurringEventOnDate(event ical.Event, date time.Time) bool {
110+
recurrenceSet, err := event.RecurrenceSet(data.tz)
111+
if err != nil {
112+
data.logger.Printf("could not get recurrence set: %s\n", err)
113+
return false
114+
}
115+
if recurrenceSet == nil {
116+
return false
117+
}
118+
119+
todayStart := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, data.tz)
120+
121+
// Obtain the next recurrence date after todayStart.
122+
nextRecurrence := recurrenceSet.After(todayStart, true)
123+
124+
// Use components of nextRecurrence to create a new date with a specific time set to midnight.
125+
recurrenceDate := time.Date(nextRecurrence.Year(), nextRecurrence.Month(), nextRecurrence.Day(), 0, 0, 0, 0, data.tz)
126+
127+
return recurrenceDate.Equal(todayStart)
128+
}
129+
130+
// Check for a duplicate and returns the right event
131+
func (data IcalData) handleDuplicates(uid string, event ical.Event, allEvents map[string]ical.Event) ical.Event {
132+
133+
newCandidate := event
134+
135+
if _, ok := allEvents[uid]; ok {
136+
137+
existingCandidate := allEvents[uid]
138+
139+
existingCandidateCreatedProp := allEvents[uid].Props.Get(ical.PropCreated)
140+
if existingCandidateCreatedProp == nil {
141+
return existingCandidate
92142
}
93-
end, err := event.DateTimeEnd(cal.tz)
143+
existingCandidateCreatedTime, err := existingCandidateCreatedProp.DateTime(data.tz)
94144
if err != nil {
95-
return []EventData{}, err
145+
return existingCandidate
96146
}
97-
// regular event
98-
if (start.After(todayStart) || start.Local() == todayStart.Local()) && start.Before(todayEnd) || (start.Before(todayStart) && end.After(todayEnd)) {
99-
selected_events = append(selected_events, event)
100-
continue
147+
148+
newCandidateCreatedProp := event.Props.Get(ical.PropCreated)
149+
if newCandidateCreatedProp == nil {
150+
return existingCandidate
101151
}
102-
// recurring event
103-
reccurenceSet, err := event.RecurrenceSet(cal.tz)
152+
153+
newCandidateCreatedTime, err := newCandidateCreatedProp.DateTime(data.tz)
104154
if err != nil {
105-
cal.Printf("could not get recurrence set: %s\n", err)
106-
continue
107-
}
108-
if reccurenceSet == nil {
109-
// no recurrence
110-
continue
111-
}
112-
if GetDateWithoutTime(reccurenceSet.After(todayStart, true)).Local() == GetDateWithoutTime(date).Local() {
113-
selected_events = append(selected_events, event)
155+
return existingCandidate
114156
}
115-
}
116157

117-
// Convert ical.Events to EventData
118-
eventDatas := make([]EventData, 0)
119-
for _, event := range selected_events {
120-
eventData, err := cal.ConvertToEventData(event, date)
121-
if err != nil {
122-
return nil, err
158+
// If there is a duplicate select the one that was created later
159+
if existingCandidateCreatedTime.After(newCandidateCreatedTime) {
160+
return existingCandidate
123161
}
124-
eventDatas = append(eventDatas, eventData)
125162
}
126163

127-
// sort events
128-
sort.SliceStable(eventDatas, func(i, j int) bool {
129-
start1 := eventDatas[i].Start
130-
start2 := eventDatas[j].Start
131-
return start1.Before(start2)
132-
})
133-
134-
return eventDatas, nil
164+
return newCandidate
135165
}
136166

137-
func GetDateWithoutTime(date time.Time) time.Time {
138-
return time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, time.Local)
167+
// Sorts events by start time
168+
func sortEvents(events []Event) {
169+
sort.SliceStable(events, func(i, j int) bool {
170+
return events[i].Start.Before(events[j].Start)
171+
})
139172
}
140-
func (cal Calendar) ConvertToEventData(icalEvent ical.Event, d time.Time) (EventData, error) {
141-
eventData := EventData{}
173+
174+
// Builds the object that is used to represent an event
175+
func (data IcalData) convertIcal(icalEvent ical.Event, date time.Time) (Event, error) {
176+
event := Event{}
142177

143178
// Handle UID
144179
uidProp := icalEvent.Props.Get(ical.PropUID)
145180
if uidProp != nil {
146-
eventData.UID = uidProp.Value
181+
event.UID = uidProp.Value
147182
} else {
148-
return eventData, fmt.Errorf("UID is missing for event %s", icalEvent.Name)
183+
return event, fmt.Errorf("UID is missing for event %s", icalEvent.Name)
149184
}
150185

151186
// Handle DTSTART
152187
startProp := icalEvent.Props.Get(ical.PropDateTimeStart)
153188
if startProp == nil {
154-
return eventData, fmt.Errorf("DTSTART is missing for event %s", icalEvent.Name)
189+
return event, fmt.Errorf("DTSTART is missing for event %s", icalEvent.Name)
155190
}
156-
eventStart, err := startProp.DateTime(cal.tz)
191+
eventStart, err := startProp.DateTime(data.tz)
157192
if err != nil {
158-
return eventData, err
193+
return event, err
159194
}
160-
eventData.Start = time.Date(d.Year(), d.Month(), d.Day(), eventStart.Hour(), eventStart.Minute(), 0, 0, d.Location())
195+
event.Start = time.Date(date.Year(), date.Month(), date.Day(), eventStart.Hour(), eventStart.Minute(), 0, 0, date.Location())
161196

162197
// Handle DTEND
163198
endProp := icalEvent.Props.Get(ical.PropDateTimeEnd)
164199
if endProp != nil {
165-
eventEnd, err := endProp.DateTime(cal.tz)
200+
eventEnd, err := endProp.DateTime(data.tz)
166201
if err != nil {
167-
return eventData, err
202+
return event, err
168203
}
169-
// Calculate the difference in days and adjust eventData.End
204+
// Calculate the difference in days and adjust Event.End
170205
daysDiff := int(eventEnd.Sub(eventStart).Hours() / 24)
171-
eventData.End = time.Date(d.Year(), d.Month(), d.Day()+daysDiff, eventEnd.Hour(), eventEnd.Minute(), 0, 0, d.Location())
206+
event.End = time.Date(date.Year(), date.Month(), date.Day()+daysDiff, eventEnd.Hour(), eventEnd.Minute(), 0, 0, date.Location())
172207
}
173208

174209
// Handle SUMMARY
175210
summaryProp := icalEvent.Props.Get(ical.PropSummary)
176211
if summaryProp != nil {
177-
eventData.Summary = summaryProp.Value
212+
event.Summary = summaryProp.Value
178213
}
179214

180215
// Handle LOCATION
181216
locationProp := icalEvent.Props.Get(ical.PropLocation)
182217
if locationProp != nil {
183-
eventData.Location = locationProp.Value
218+
event.Location = locationProp.Value
184219
}
185220

186221
// Handle DESCRIPTION
187222
descriptionProp := icalEvent.Props.Get(ical.PropDescription)
188223
if descriptionProp != nil {
189-
eventData.Description = descriptionProp.Value
224+
event.Description = descriptionProp.Value
190225
} else {
191-
eventData.Description = ""
226+
event.Description = ""
192227
}
193228

194-
return eventData, nil
229+
return event, nil
195230
}

0 commit comments

Comments
 (0)