active-icalendar-events 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/active-icalendar-events.rb +149 -116
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ee38fd75cc71289c8cf434fcf1d107f00619e3fd1b4556a775ea8c62d7ec129d
|
4
|
+
data.tar.gz: 64c6215c1072a70e6d67f50c5040c1e358a7320162c05ef0851a433b40b356b3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cc21a38b8102ec9ae3983c040f897ec73b15188da380f8a6ebcfb08163b80157f0e2e7b05c6dfd5a7c7a6bc0fe4255c27d79ee99b20d2e34c7888f9a936c7336
|
7
|
+
data.tar.gz: d68e25d312753fe97b67c5471a78fb7172ca29280cafa298c48fddce2400601a77824ef125e40c1ef07266948d86b9b1266591f2ec440090558bd96b2e877a95
|
@@ -1,4 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'active_support'
|
4
5
|
require 'active_support/core_ext'
|
@@ -51,36 +52,36 @@ module ActiveIcalendarEvents
|
|
51
52
|
active_events = Set.new
|
52
53
|
|
53
54
|
format_icalendar_data(icalendar_data).each do |_, events|
|
54
|
-
recurrence_definition = events.select
|
55
|
+
recurrence_definition = events.select do |e|
|
55
56
|
!e[:recurrence_rule].empty? || !e[:recurrence_dates].empty?
|
56
|
-
|
57
|
+
end
|
57
58
|
if recurrence_definition.size > 1
|
58
|
-
raise
|
59
|
+
raise 'Should only have one event that defines the recurrence in a group'
|
59
60
|
elsif recurrence_definition.size == 1
|
60
61
|
r = recurrence_definition.first
|
61
62
|
if r[:recurrence_rule].size > 1
|
62
|
-
raise
|
63
|
+
raise 'Multiple recurrence rules not supported'
|
63
64
|
elsif r[:recurrence_rule].size == 1
|
64
65
|
# TODO: Validate the overrides
|
65
66
|
active_events << get_active_event_for_datetime(
|
66
|
-
:
|
67
|
-
:
|
68
|
-
:
|
69
|
-
:
|
70
|
-
:
|
71
|
-
:
|
72
|
-
:
|
73
|
-
:
|
67
|
+
datetime: datetime,
|
68
|
+
name: r[:name],
|
69
|
+
event_start: r[:event_start],
|
70
|
+
event_end: r[:event_end],
|
71
|
+
recurrence_rule: r[:recurrence_rule].first,
|
72
|
+
recurrence_dates: r[:recurrence_dates],
|
73
|
+
excluding_dates: r[:excluding_dates],
|
74
|
+
overrides: events.reject { |e| e == r }.group_by { |e| e[:recurrence_id] }
|
74
75
|
)
|
75
76
|
else
|
76
77
|
# TODO: Haven't bothered implementing this as Google Calendar doesn't seem to use these
|
77
|
-
raise
|
78
|
+
raise 'Not yet implemented when only recurrence_dates are provided'
|
78
79
|
end
|
79
80
|
else
|
80
81
|
# Non reccurring events
|
81
|
-
events.each
|
82
|
-
active_events.add(e[:name]) if
|
83
|
-
|
82
|
+
events.each do |e|
|
83
|
+
active_events.add(e[:name]) if event_active?(datetime, e[:event_start], e[:event_end])
|
84
|
+
end
|
84
85
|
end
|
85
86
|
end
|
86
87
|
|
@@ -90,34 +91,67 @@ module ActiveIcalendarEvents
|
|
90
91
|
active_events.to_a
|
91
92
|
end
|
92
93
|
|
94
|
+
def timezone_for_event(event)
|
95
|
+
if event.parent.timezones.empty?
|
96
|
+
ActiveSupport::TimeZone.new(event.parent.custom_properties['x_wr_timezone'].first.to_s)
|
97
|
+
else
|
98
|
+
ActiveSupport::TimeZone.new(event.parent.timezones.first.tzid.to_s)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
93
102
|
def format_icalendar_data(icalendar_data)
|
94
|
-
icalendar_data.first.events.map
|
103
|
+
icalendar_data.first.events.map do |e|
|
95
104
|
event_start = e.dtstart
|
96
105
|
if event_start.is_a?(Icalendar::Values::Date)
|
97
|
-
timezone
|
106
|
+
timezone ||= timezone_for_event(e)
|
98
107
|
event_start = timezone.local(event_start.year, event_start.month, event_start.day)
|
99
108
|
end
|
100
109
|
|
101
110
|
event_end = e.dtend
|
102
111
|
if event_end.is_a?(Icalendar::Values::Date)
|
103
|
-
timezone
|
112
|
+
timezone ||= timezone_for_event(e)
|
104
113
|
event_end = timezone.local(event_end.year, event_end.month, event_end.day)
|
105
114
|
end
|
106
115
|
|
116
|
+
excluding_dates = e.exdate.map do |d|
|
117
|
+
if d.is_a?(Icalendar::Values::Date)
|
118
|
+
timezone ||= timezone_for_event(e)
|
119
|
+
timezone.local(d.year, d.month, d.day)
|
120
|
+
else
|
121
|
+
d
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
recurrence_dates = e.rdate.map do |d|
|
126
|
+
if d.is_a?(Icalendar::Values::Date)
|
127
|
+
timezone ||= timezone_for_event(e)
|
128
|
+
timezone.local(d.year, d.month, d.day)
|
129
|
+
else
|
130
|
+
d
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
e.rrule.each do |rrule|
|
135
|
+
unless rrule.until.nil?
|
136
|
+
timezone ||= timezone_for_event(e)
|
137
|
+
rrule.until = timezone.parse(rrule.until)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
107
141
|
{
|
108
142
|
name: e.summary,
|
109
143
|
event_start: event_start,
|
110
144
|
event_end: event_end,
|
111
145
|
recurrence_rule: e.rrule,
|
112
|
-
recurrence_dates:
|
113
|
-
excluding_dates:
|
146
|
+
recurrence_dates: recurrence_dates,
|
147
|
+
excluding_dates: excluding_dates,
|
114
148
|
recurrence_id: e.recurrence_id,
|
115
149
|
uid: e.uid
|
116
150
|
}
|
117
|
-
|
151
|
+
end.group_by { |e| e[:uid] }
|
118
152
|
end
|
119
153
|
|
120
|
-
def
|
154
|
+
def event_active?(datetime, event_start, event_end)
|
121
155
|
event_start <= datetime.to_time &&
|
122
156
|
event_end > datetime.to_time
|
123
157
|
end
|
@@ -130,14 +164,14 @@ module ActiveIcalendarEvents
|
|
130
164
|
!count.nil? && considered_count > count
|
131
165
|
end
|
132
166
|
|
133
|
-
def
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
167
|
+
def daily_event_active_for_datetime?(datetime,
|
168
|
+
event_start,
|
169
|
+
event_end,
|
170
|
+
until_datetime,
|
171
|
+
count,
|
172
|
+
interval,
|
173
|
+
excluding_dates,
|
174
|
+
overridden_dates)
|
141
175
|
event_start_considered = event_start
|
142
176
|
event_end_considered = event_end
|
143
177
|
considered_count = 1
|
@@ -145,7 +179,7 @@ module ActiveIcalendarEvents
|
|
145
179
|
!instance_count_exceeded?(considered_count, count) &&
|
146
180
|
event_start_considered <= datetime
|
147
181
|
|
148
|
-
if
|
182
|
+
if event_active?(datetime, event_start_considered, event_end_considered)
|
149
183
|
return !excluding_dates.include?(event_start_considered) &&
|
150
184
|
!overridden_dates.include?(event_start_considered)
|
151
185
|
end
|
@@ -153,34 +187,35 @@ module ActiveIcalendarEvents
|
|
153
187
|
# We consider both active dates and excluded dates for the recurrence count
|
154
188
|
considered_count += 1
|
155
189
|
|
156
|
-
event_start_considered
|
157
|
-
event_end_considered
|
190
|
+
event_start_considered += interval.days
|
191
|
+
event_end_considered += interval.days
|
158
192
|
end
|
159
193
|
|
160
194
|
false
|
161
195
|
end
|
162
196
|
|
163
|
-
def
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
197
|
+
def weekly_event_active_for_datetime?(datetime,
|
198
|
+
event_start,
|
199
|
+
event_end,
|
200
|
+
until_datetime,
|
201
|
+
count,
|
202
|
+
interval,
|
203
|
+
by_day,
|
204
|
+
excluding_dates,
|
205
|
+
overridden_dates)
|
172
206
|
event_start_considered = event_start
|
173
207
|
event_end_considered = event_end
|
174
208
|
considered_count = 1
|
175
|
-
|
209
|
+
until instance_count_exceeded?(considered_count, count)
|
176
210
|
|
211
|
+
# NOTE: Google Calendar does not appear to produce weekly events that do not specify a "by_day" array, so this path is untested
|
177
212
|
if by_day.empty?
|
178
213
|
if until_datetime_passed?(event_start_considered, until_datetime) ||
|
179
214
|
event_start_considered > datetime
|
180
215
|
return false
|
181
216
|
end
|
182
217
|
|
183
|
-
if
|
218
|
+
if event_active?(datetime, event_start_considered, event_end_considered)
|
184
219
|
return !excluding_dates.include?(event_start_considered) &&
|
185
220
|
!overridden_dates.include?(event_start_considered)
|
186
221
|
end
|
@@ -189,11 +224,14 @@ module ActiveIcalendarEvents
|
|
189
224
|
considered_count += 1
|
190
225
|
else
|
191
226
|
week_event_start_considered =
|
192
|
-
event_start_considered.monday?
|
193
|
-
|
227
|
+
if event_start_considered.monday?
|
228
|
+
event_start_considered
|
229
|
+
else
|
230
|
+
event_start_considered.prev_occurring(:monday)
|
231
|
+
end
|
194
232
|
week_event_end_considered = week_event_start_considered + (event_end.to_time - event_start.to_time).seconds
|
195
233
|
|
196
|
-
(1..7).each
|
234
|
+
(1..7).each do |_|
|
197
235
|
if week_event_start_considered >= event_start
|
198
236
|
if until_datetime_passed?(week_event_start_considered, until_datetime) ||
|
199
237
|
instance_count_exceeded?(considered_count, count) ||
|
@@ -201,10 +239,10 @@ module ActiveIcalendarEvents
|
|
201
239
|
return false
|
202
240
|
end
|
203
241
|
|
204
|
-
day_code = week_event_start_considered.strftime(
|
242
|
+
day_code = week_event_start_considered.strftime('%^a').chop
|
205
243
|
|
206
244
|
if by_day.include?(day_code)
|
207
|
-
if
|
245
|
+
if event_active?(datetime, week_event_start_considered, week_event_end_considered)
|
208
246
|
return !excluding_dates.include?(week_event_start_considered) &&
|
209
247
|
!overridden_dates.include?(week_event_start_considered)
|
210
248
|
end
|
@@ -214,23 +252,26 @@ module ActiveIcalendarEvents
|
|
214
252
|
end
|
215
253
|
end
|
216
254
|
|
217
|
-
week_event_start_considered
|
218
|
-
week_event_end_considered
|
219
|
-
|
255
|
+
week_event_start_considered += 1.days
|
256
|
+
week_event_end_considered += 1.days
|
257
|
+
end
|
220
258
|
end
|
221
259
|
|
222
|
-
event_start_considered
|
223
|
-
event_end_considered
|
260
|
+
event_start_considered += interval.weeks
|
261
|
+
event_end_considered += interval.weeks
|
224
262
|
end
|
225
263
|
|
226
264
|
false
|
227
265
|
end
|
228
266
|
|
267
|
+
# Get the beginning of the month, maintaining the timestamp
|
268
|
+
def beginning_of_month(datetime)
|
269
|
+
datetime - (datetime.day - 1).days
|
270
|
+
end
|
271
|
+
|
229
272
|
def get_nth_day_in_month(datetime, day)
|
230
|
-
matches = day.match
|
231
|
-
if matches.nil?
|
232
|
-
raise RuntimeError, "Unexpected by_day format found"
|
233
|
-
end
|
273
|
+
matches = day.match(/^([0-9]+)([A-Z]+)$/)
|
274
|
+
raise 'Unexpected by_day format found' if matches.nil?
|
234
275
|
|
235
276
|
number, day_code = matches.captures
|
236
277
|
|
@@ -250,32 +291,30 @@ module ActiveIcalendarEvents
|
|
250
291
|
when 'SU'
|
251
292
|
:sunday
|
252
293
|
else
|
253
|
-
raise
|
294
|
+
raise 'Unexpected day code used'
|
254
295
|
end
|
255
296
|
|
256
|
-
target_day = datetime
|
297
|
+
target_day = beginning_of_month(datetime)
|
257
298
|
|
258
|
-
if target_day.strftime(
|
259
|
-
target_day = target_day.next_occurring(day_label)
|
260
|
-
end
|
299
|
+
target_day = target_day.next_occurring(day_label) if target_day.strftime('%^a').chop != day_code
|
261
300
|
|
262
|
-
(2..number.to_i).each
|
301
|
+
(2..number.to_i).each do |_|
|
263
302
|
target_day = target_day.next_occurring(day_label)
|
264
|
-
|
303
|
+
end
|
265
304
|
|
266
305
|
target_day
|
267
306
|
end
|
268
307
|
|
269
|
-
def
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
308
|
+
def monthly_event_active_for_datetime?(datetime,
|
309
|
+
event_start,
|
310
|
+
event_end,
|
311
|
+
until_datetime,
|
312
|
+
count,
|
313
|
+
interval,
|
314
|
+
by_day,
|
315
|
+
by_month_day,
|
316
|
+
excluding_dates,
|
317
|
+
overridden_dates)
|
279
318
|
# TODO: We will ignore the contents of "by_month_day" for now and assume
|
280
319
|
# always contains one number which is the same as the day of
|
281
320
|
# "event_start". We additionally assume that "by_day" will only contain
|
@@ -288,7 +327,7 @@ module ActiveIcalendarEvents
|
|
288
327
|
!instance_count_exceeded?(considered_count, count) &&
|
289
328
|
event_start_considered <= datetime
|
290
329
|
|
291
|
-
if
|
330
|
+
if event_active?(datetime, event_start_considered, event_end_considered)
|
292
331
|
return !excluding_dates.include?(event_start_considered) &&
|
293
332
|
!overridden_dates.include?(event_start_considered)
|
294
333
|
end
|
@@ -297,11 +336,11 @@ module ActiveIcalendarEvents
|
|
297
336
|
considered_count += 1
|
298
337
|
|
299
338
|
if by_day.nil? || by_day.empty?
|
300
|
-
event_start_considered
|
301
|
-
event_end_considered
|
339
|
+
event_start_considered += interval.month
|
340
|
+
event_end_considered += interval.month
|
302
341
|
else
|
303
342
|
event_start_considered =
|
304
|
-
get_nth_day_in_month(event_start_considered
|
343
|
+
get_nth_day_in_month(beginning_of_month(event_start_considered) + interval.month,
|
305
344
|
by_day.first)
|
306
345
|
event_end_considered = event_start_considered + (event_end.to_time - event_start.to_time).seconds
|
307
346
|
end
|
@@ -310,14 +349,14 @@ module ActiveIcalendarEvents
|
|
310
349
|
false
|
311
350
|
end
|
312
351
|
|
313
|
-
def
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
352
|
+
def yearly_event_active_for_datetime?(datetime,
|
353
|
+
event_start,
|
354
|
+
event_end,
|
355
|
+
until_datetime,
|
356
|
+
count,
|
357
|
+
interval,
|
358
|
+
excluding_dates,
|
359
|
+
overridden_dates)
|
321
360
|
event_start_considered = event_start
|
322
361
|
event_end_considered = event_end
|
323
362
|
considered_count = 1
|
@@ -325,7 +364,7 @@ module ActiveIcalendarEvents
|
|
325
364
|
!instance_count_exceeded?(considered_count, count) &&
|
326
365
|
event_start_considered <= datetime
|
327
366
|
|
328
|
-
if
|
367
|
+
if event_active?(datetime, event_start_considered, event_end_considered)
|
329
368
|
return !excluding_dates.include?(event_start_considered) &&
|
330
369
|
!overridden_dates.include?(event_start_considered)
|
331
370
|
end
|
@@ -333,8 +372,8 @@ module ActiveIcalendarEvents
|
|
333
372
|
# We consider both active dates and excluded dates for the recurrence count
|
334
373
|
considered_count += 1
|
335
374
|
|
336
|
-
event_start_considered
|
337
|
-
event_end_considered
|
375
|
+
event_start_considered += interval.years
|
376
|
+
event_end_considered += interval.years
|
338
377
|
end
|
339
378
|
|
340
379
|
false
|
@@ -349,54 +388,48 @@ module ActiveIcalendarEvents
|
|
349
388
|
excluding_dates: [],
|
350
389
|
overrides:)
|
351
390
|
# Can return early if one of the overrides matches as they always take precendence
|
352
|
-
overrides.values.flatten.each
|
353
|
-
return e[:name] if e[:event_start]
|
354
|
-
|
355
|
-
}
|
391
|
+
overrides.values.flatten.each do |e|
|
392
|
+
return e[:name] if event_active?(datetime, e[:event_start], e[:event_end])
|
393
|
+
end
|
356
394
|
|
357
395
|
# Can return early if one of the recurrence dates matches and is not overridden
|
358
396
|
# Note: I've just made an assumption about how this data could be presented.
|
359
397
|
# Google Calendar does not seem to create rdates, only rrules.
|
360
|
-
(recurrence_dates - overrides.keys).each
|
361
|
-
recurrence_event_end = recurrence_event_start + (event_end.to_time - event_start.to_time)
|
362
|
-
return name if
|
363
|
-
}
|
364
|
-
|
365
|
-
until_datetime = nil
|
366
|
-
if !recurrence_rule.until.nil?
|
367
|
-
until_datetime = DateTime.parse(recurrence_rule.until)
|
398
|
+
(recurrence_dates - overrides.keys).each do |recurrence_event_start|
|
399
|
+
recurrence_event_end = recurrence_event_start + (event_end.to_time - event_start.to_time).seconds
|
400
|
+
return name if event_active?(datetime, recurrence_event_start, recurrence_event_end)
|
368
401
|
end
|
369
402
|
|
370
403
|
case recurrence_rule.frequency
|
371
|
-
when
|
372
|
-
return name if
|
404
|
+
when 'DAILY'
|
405
|
+
return name if daily_event_active_for_datetime?(
|
373
406
|
datetime,
|
374
407
|
event_start,
|
375
408
|
event_end,
|
376
|
-
|
409
|
+
recurrence_rule.until,
|
377
410
|
recurrence_rule.count,
|
378
411
|
recurrence_rule.interval.nil? ? 1 : recurrence_rule.interval,
|
379
412
|
excluding_dates,
|
380
413
|
overrides.keys
|
381
414
|
)
|
382
|
-
when
|
383
|
-
return name if
|
415
|
+
when 'WEEKLY'
|
416
|
+
return name if weekly_event_active_for_datetime?(
|
384
417
|
datetime,
|
385
418
|
event_start,
|
386
419
|
event_end,
|
387
|
-
|
420
|
+
recurrence_rule.until,
|
388
421
|
recurrence_rule.count,
|
389
422
|
recurrence_rule.interval.nil? ? 1 : recurrence_rule.interval,
|
390
423
|
recurrence_rule.by_day,
|
391
424
|
excluding_dates,
|
392
425
|
overrides.keys
|
393
426
|
)
|
394
|
-
when
|
395
|
-
return name if
|
427
|
+
when 'MONTHLY'
|
428
|
+
return name if monthly_event_active_for_datetime?(
|
396
429
|
datetime,
|
397
430
|
event_start,
|
398
431
|
event_end,
|
399
|
-
|
432
|
+
recurrence_rule.until,
|
400
433
|
recurrence_rule.count,
|
401
434
|
recurrence_rule.interval.nil? ? 1 : recurrence_rule.interval,
|
402
435
|
recurrence_rule.by_day,
|
@@ -404,19 +437,19 @@ module ActiveIcalendarEvents
|
|
404
437
|
excluding_dates,
|
405
438
|
overrides.keys
|
406
439
|
)
|
407
|
-
when
|
408
|
-
return name if
|
440
|
+
when 'YEARLY'
|
441
|
+
return name if yearly_event_active_for_datetime?(
|
409
442
|
datetime,
|
410
443
|
event_start,
|
411
444
|
event_end,
|
412
|
-
|
445
|
+
recurrence_rule.until,
|
413
446
|
recurrence_rule.count,
|
414
447
|
recurrence_rule.interval.nil? ? 1 : recurrence_rule.interval,
|
415
448
|
excluding_dates,
|
416
449
|
overrides.keys
|
417
450
|
)
|
418
451
|
else
|
419
|
-
throw RuntimeError,
|
452
|
+
throw RuntimeError, 'Invalid event frequency'
|
420
453
|
end
|
421
454
|
|
422
455
|
nil
|