@@ -8,17 +8,16 @@ import (
8
8
"time"
9
9
10
10
"github.com/emersion/go-ical"
11
- // "github.com/teambition/rrule-go"
12
11
)
13
12
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
19
18
}
20
19
21
- type EventData struct {
20
+ type Event struct {
22
21
UID string
23
22
Start time.Time
24
23
End time.Time
@@ -27,169 +26,205 @@ type EventData struct {
27
26
Description string
28
27
}
29
28
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 }
32
32
33
+ // Download the ICS file
33
34
resp , err := http .Get (url )
34
35
if err != nil {
35
- return calendar , err
36
+ return data , err
36
37
}
37
38
defer resp .Body .Close ()
38
39
40
+ // Parse the ICS file
39
41
parser := ical .NewDecoder (resp .Body )
40
-
41
- cal , err := parser .Decode ()
42
+ parsedData , err := parser .Decode ()
42
43
if err != nil {
43
- return calendar , err
44
+ return data , err
44
45
}
45
- calendar . Calendar = cal
46
- calendar . Logger = l
47
- return calendar , nil
46
+
47
+ data . parsed = parsedData
48
+ return data , nil
48
49
}
49
50
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 () {
53
59
54
- todayStart := GetDateWithoutTime (date )
55
- todayEnd := todayStart .Add (24 * time .Hour )
56
- for _ , event := range cal .Events () {
57
60
uid := event .Props .Get (ical .PropUID ).Value
61
+ allEvents [uid ] = data .handleDuplicates (uid , event , allEvents )
58
62
59
- // check for doubles via uid
60
- if _ , ok := all_events [uid ]; ok {
63
+ }
61
64
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 {
70
66
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 ) {
75
70
76
- created_new , err := createdNewProp . DateTime ( cal . tz )
71
+ convertedEvent , err := data . convertIcal ( event , date )
77
72
if err != nil {
78
- continue
79
- }
80
-
81
- if created_existing .After (created_new ) {
82
- continue
73
+ return nil , err
83
74
}
75
+ selectedEvents = append (selectedEvents , convertedEvent )
84
76
}
85
- all_events [event .Props .Get (ical .PropUID ).Value ] = event
86
77
}
87
78
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
92
142
}
93
- end , err := event . DateTimeEnd ( cal .tz )
143
+ existingCandidateCreatedTime , err := existingCandidateCreatedProp . DateTime ( data .tz )
94
144
if err != nil {
95
- return [] EventData {}, err
145
+ return existingCandidate
96
146
}
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
101
151
}
102
- // recurring event
103
- reccurenceSet , err := event . RecurrenceSet ( cal .tz )
152
+
153
+ newCandidateCreatedTime , err := newCandidateCreatedProp . DateTime ( data .tz )
104
154
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
114
156
}
115
- }
116
157
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
123
161
}
124
- eventDatas = append (eventDatas , eventData )
125
162
}
126
163
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
135
165
}
136
166
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
+ })
139
172
}
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 {}
142
177
143
178
// Handle UID
144
179
uidProp := icalEvent .Props .Get (ical .PropUID )
145
180
if uidProp != nil {
146
- eventData .UID = uidProp .Value
181
+ event .UID = uidProp .Value
147
182
} 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 )
149
184
}
150
185
151
186
// Handle DTSTART
152
187
startProp := icalEvent .Props .Get (ical .PropDateTimeStart )
153
188
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 )
155
190
}
156
- eventStart , err := startProp .DateTime (cal .tz )
191
+ eventStart , err := startProp .DateTime (data .tz )
157
192
if err != nil {
158
- return eventData , err
193
+ return event , err
159
194
}
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 ())
161
196
162
197
// Handle DTEND
163
198
endProp := icalEvent .Props .Get (ical .PropDateTimeEnd )
164
199
if endProp != nil {
165
- eventEnd , err := endProp .DateTime (cal .tz )
200
+ eventEnd , err := endProp .DateTime (data .tz )
166
201
if err != nil {
167
- return eventData , err
202
+ return event , err
168
203
}
169
- // Calculate the difference in days and adjust eventData .End
204
+ // Calculate the difference in days and adjust Event .End
170
205
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 ())
172
207
}
173
208
174
209
// Handle SUMMARY
175
210
summaryProp := icalEvent .Props .Get (ical .PropSummary )
176
211
if summaryProp != nil {
177
- eventData .Summary = summaryProp .Value
212
+ event .Summary = summaryProp .Value
178
213
}
179
214
180
215
// Handle LOCATION
181
216
locationProp := icalEvent .Props .Get (ical .PropLocation )
182
217
if locationProp != nil {
183
- eventData .Location = locationProp .Value
218
+ event .Location = locationProp .Value
184
219
}
185
220
186
221
// Handle DESCRIPTION
187
222
descriptionProp := icalEvent .Props .Get (ical .PropDescription )
188
223
if descriptionProp != nil {
189
- eventData .Description = descriptionProp .Value
224
+ event .Description = descriptionProp .Value
190
225
} else {
191
- eventData .Description = ""
226
+ event .Description = ""
192
227
}
193
228
194
- return eventData , nil
229
+ return event , nil
195
230
}
0 commit comments