active-icalendar-events 0.1.1 → 0.2.0
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 → active_icalendar_events.rb} +161 -116
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2c58972dd8113dffe13dc9434ac99480bf4bbffcc6013e8bbc082a38e0487729
|
4
|
+
data.tar.gz: 29e515e96bad5067907bcf7b43bf375ce2a01df43de0d743f7bb31c52566f483
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dea7414593d274bdb9c3f20e27e3ae12c1eca6764e8ef0cee122c21562edc36ab67977d4809d3fe19abd0fb9c771aaf4a491db8ae0aaed2279598a7be0daeac1
|
7
|
+
data.tar.gz: c9c1321759cd3080f79572e3ddc595f8b066a8422215dc27479ef1c547e84d1e10ae05e24f0a0687cc8a81ceffa2d5f477d308ebd24a8a6ffb8cef08de6e9adf
|
@@ -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,22 +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|
|
104
|
+
event_start = e.dtstart
|
105
|
+
if event_start.is_a?(Icalendar::Values::Date)
|
106
|
+
timezone ||= timezone_for_event(e)
|
107
|
+
event_start = timezone.local(event_start.year, event_start.month, event_start.day)
|
108
|
+
end
|
109
|
+
|
110
|
+
event_end = e.dtend
|
111
|
+
if event_end.is_a?(Icalendar::Values::Date)
|
112
|
+
timezone ||= timezone_for_event(e)
|
113
|
+
event_end = timezone.local(event_end.year, event_end.month, event_end.day)
|
114
|
+
end
|
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
|
+
|
95
141
|
{
|
96
142
|
name: e.summary,
|
97
|
-
event_start:
|
98
|
-
event_end:
|
143
|
+
event_start: event_start,
|
144
|
+
event_end: event_end,
|
99
145
|
recurrence_rule: e.rrule,
|
100
|
-
recurrence_dates:
|
101
|
-
excluding_dates:
|
146
|
+
recurrence_dates: recurrence_dates,
|
147
|
+
excluding_dates: excluding_dates,
|
102
148
|
recurrence_id: e.recurrence_id,
|
103
149
|
uid: e.uid
|
104
150
|
}
|
105
|
-
|
151
|
+
end.group_by { |e| e[:uid] }
|
106
152
|
end
|
107
153
|
|
108
|
-
def
|
154
|
+
def event_active?(datetime, event_start, event_end)
|
109
155
|
event_start <= datetime.to_time &&
|
110
156
|
event_end > datetime.to_time
|
111
157
|
end
|
@@ -118,14 +164,14 @@ module ActiveIcalendarEvents
|
|
118
164
|
!count.nil? && considered_count > count
|
119
165
|
end
|
120
166
|
|
121
|
-
def
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
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)
|
129
175
|
event_start_considered = event_start
|
130
176
|
event_end_considered = event_end
|
131
177
|
considered_count = 1
|
@@ -133,7 +179,7 @@ module ActiveIcalendarEvents
|
|
133
179
|
!instance_count_exceeded?(considered_count, count) &&
|
134
180
|
event_start_considered <= datetime
|
135
181
|
|
136
|
-
if
|
182
|
+
if event_active?(datetime, event_start_considered, event_end_considered)
|
137
183
|
return !excluding_dates.include?(event_start_considered) &&
|
138
184
|
!overridden_dates.include?(event_start_considered)
|
139
185
|
end
|
@@ -141,34 +187,35 @@ module ActiveIcalendarEvents
|
|
141
187
|
# We consider both active dates and excluded dates for the recurrence count
|
142
188
|
considered_count += 1
|
143
189
|
|
144
|
-
event_start_considered
|
145
|
-
event_end_considered
|
190
|
+
event_start_considered += interval.days
|
191
|
+
event_end_considered += interval.days
|
146
192
|
end
|
147
193
|
|
148
194
|
false
|
149
195
|
end
|
150
196
|
|
151
|
-
def
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
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)
|
160
206
|
event_start_considered = event_start
|
161
207
|
event_end_considered = event_end
|
162
208
|
considered_count = 1
|
163
|
-
|
209
|
+
until instance_count_exceeded?(considered_count, count)
|
164
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
|
165
212
|
if by_day.empty?
|
166
213
|
if until_datetime_passed?(event_start_considered, until_datetime) ||
|
167
214
|
event_start_considered > datetime
|
168
215
|
return false
|
169
216
|
end
|
170
217
|
|
171
|
-
if
|
218
|
+
if event_active?(datetime, event_start_considered, event_end_considered)
|
172
219
|
return !excluding_dates.include?(event_start_considered) &&
|
173
220
|
!overridden_dates.include?(event_start_considered)
|
174
221
|
end
|
@@ -177,11 +224,14 @@ module ActiveIcalendarEvents
|
|
177
224
|
considered_count += 1
|
178
225
|
else
|
179
226
|
week_event_start_considered =
|
180
|
-
event_start_considered.monday?
|
181
|
-
|
227
|
+
if event_start_considered.monday?
|
228
|
+
event_start_considered
|
229
|
+
else
|
230
|
+
event_start_considered.prev_occurring(:monday)
|
231
|
+
end
|
182
232
|
week_event_end_considered = week_event_start_considered + (event_end.to_time - event_start.to_time).seconds
|
183
233
|
|
184
|
-
(1..7).each
|
234
|
+
(1..7).each do |_|
|
185
235
|
if week_event_start_considered >= event_start
|
186
236
|
if until_datetime_passed?(week_event_start_considered, until_datetime) ||
|
187
237
|
instance_count_exceeded?(considered_count, count) ||
|
@@ -189,10 +239,10 @@ module ActiveIcalendarEvents
|
|
189
239
|
return false
|
190
240
|
end
|
191
241
|
|
192
|
-
day_code = week_event_start_considered.strftime(
|
242
|
+
day_code = week_event_start_considered.strftime('%^a').chop
|
193
243
|
|
194
244
|
if by_day.include?(day_code)
|
195
|
-
if
|
245
|
+
if event_active?(datetime, week_event_start_considered, week_event_end_considered)
|
196
246
|
return !excluding_dates.include?(week_event_start_considered) &&
|
197
247
|
!overridden_dates.include?(week_event_start_considered)
|
198
248
|
end
|
@@ -202,23 +252,26 @@ module ActiveIcalendarEvents
|
|
202
252
|
end
|
203
253
|
end
|
204
254
|
|
205
|
-
week_event_start_considered
|
206
|
-
week_event_end_considered
|
207
|
-
|
255
|
+
week_event_start_considered += 1.days
|
256
|
+
week_event_end_considered += 1.days
|
257
|
+
end
|
208
258
|
end
|
209
259
|
|
210
|
-
event_start_considered
|
211
|
-
event_end_considered
|
260
|
+
event_start_considered += interval.weeks
|
261
|
+
event_end_considered += interval.weeks
|
212
262
|
end
|
213
263
|
|
214
264
|
false
|
215
265
|
end
|
216
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
|
+
|
217
272
|
def get_nth_day_in_month(datetime, day)
|
218
|
-
matches = day.match
|
219
|
-
if matches.nil?
|
220
|
-
raise RuntimeError, "Unexpected by_day format found"
|
221
|
-
end
|
273
|
+
matches = day.match(/^([0-9]+)([A-Z]+)$/)
|
274
|
+
raise 'Unexpected by_day format found' if matches.nil?
|
222
275
|
|
223
276
|
number, day_code = matches.captures
|
224
277
|
|
@@ -238,32 +291,30 @@ module ActiveIcalendarEvents
|
|
238
291
|
when 'SU'
|
239
292
|
:sunday
|
240
293
|
else
|
241
|
-
raise
|
294
|
+
raise 'Unexpected day code used'
|
242
295
|
end
|
243
296
|
|
244
|
-
target_day = datetime
|
297
|
+
target_day = beginning_of_month(datetime)
|
245
298
|
|
246
|
-
if target_day.strftime(
|
247
|
-
target_day = target_day.next_occurring(day_label)
|
248
|
-
end
|
299
|
+
target_day = target_day.next_occurring(day_label) if target_day.strftime('%^a').chop != day_code
|
249
300
|
|
250
|
-
(2..number.to_i).each
|
301
|
+
(2..number.to_i).each do |_|
|
251
302
|
target_day = target_day.next_occurring(day_label)
|
252
|
-
|
303
|
+
end
|
253
304
|
|
254
305
|
target_day
|
255
306
|
end
|
256
307
|
|
257
|
-
def
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
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)
|
267
318
|
# TODO: We will ignore the contents of "by_month_day" for now and assume
|
268
319
|
# always contains one number which is the same as the day of
|
269
320
|
# "event_start". We additionally assume that "by_day" will only contain
|
@@ -276,7 +327,7 @@ module ActiveIcalendarEvents
|
|
276
327
|
!instance_count_exceeded?(considered_count, count) &&
|
277
328
|
event_start_considered <= datetime
|
278
329
|
|
279
|
-
if
|
330
|
+
if event_active?(datetime, event_start_considered, event_end_considered)
|
280
331
|
return !excluding_dates.include?(event_start_considered) &&
|
281
332
|
!overridden_dates.include?(event_start_considered)
|
282
333
|
end
|
@@ -285,11 +336,11 @@ module ActiveIcalendarEvents
|
|
285
336
|
considered_count += 1
|
286
337
|
|
287
338
|
if by_day.nil? || by_day.empty?
|
288
|
-
event_start_considered
|
289
|
-
event_end_considered
|
339
|
+
event_start_considered += interval.month
|
340
|
+
event_end_considered += interval.month
|
290
341
|
else
|
291
342
|
event_start_considered =
|
292
|
-
get_nth_day_in_month(event_start_considered
|
343
|
+
get_nth_day_in_month(beginning_of_month(event_start_considered) + interval.month,
|
293
344
|
by_day.first)
|
294
345
|
event_end_considered = event_start_considered + (event_end.to_time - event_start.to_time).seconds
|
295
346
|
end
|
@@ -298,14 +349,14 @@ module ActiveIcalendarEvents
|
|
298
349
|
false
|
299
350
|
end
|
300
351
|
|
301
|
-
def
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
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)
|
309
360
|
event_start_considered = event_start
|
310
361
|
event_end_considered = event_end
|
311
362
|
considered_count = 1
|
@@ -313,7 +364,7 @@ module ActiveIcalendarEvents
|
|
313
364
|
!instance_count_exceeded?(considered_count, count) &&
|
314
365
|
event_start_considered <= datetime
|
315
366
|
|
316
|
-
if
|
367
|
+
if event_active?(datetime, event_start_considered, event_end_considered)
|
317
368
|
return !excluding_dates.include?(event_start_considered) &&
|
318
369
|
!overridden_dates.include?(event_start_considered)
|
319
370
|
end
|
@@ -321,8 +372,8 @@ module ActiveIcalendarEvents
|
|
321
372
|
# We consider both active dates and excluded dates for the recurrence count
|
322
373
|
considered_count += 1
|
323
374
|
|
324
|
-
event_start_considered
|
325
|
-
event_end_considered
|
375
|
+
event_start_considered += interval.years
|
376
|
+
event_end_considered += interval.years
|
326
377
|
end
|
327
378
|
|
328
379
|
false
|
@@ -337,54 +388,48 @@ module ActiveIcalendarEvents
|
|
337
388
|
excluding_dates: [],
|
338
389
|
overrides:)
|
339
390
|
# Can return early if one of the overrides matches as they always take precendence
|
340
|
-
overrides.values.flatten.each
|
341
|
-
return e[:name] if e[:event_start]
|
342
|
-
|
343
|
-
}
|
391
|
+
overrides.values.flatten.each do |e|
|
392
|
+
return e[:name] if event_active?(datetime, e[:event_start], e[:event_end])
|
393
|
+
end
|
344
394
|
|
345
395
|
# Can return early if one of the recurrence dates matches and is not overridden
|
346
396
|
# Note: I've just made an assumption about how this data could be presented.
|
347
397
|
# Google Calendar does not seem to create rdates, only rrules.
|
348
|
-
(recurrence_dates - overrides.keys).each
|
349
|
-
recurrence_event_end = recurrence_event_start + (event_end.to_time - event_start.to_time)
|
350
|
-
return name if
|
351
|
-
}
|
352
|
-
|
353
|
-
until_datetime = nil
|
354
|
-
if !recurrence_rule.until.nil?
|
355
|
-
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)
|
356
401
|
end
|
357
402
|
|
358
403
|
case recurrence_rule.frequency
|
359
|
-
when
|
360
|
-
return name if
|
404
|
+
when 'DAILY'
|
405
|
+
return name if daily_event_active_for_datetime?(
|
361
406
|
datetime,
|
362
407
|
event_start,
|
363
408
|
event_end,
|
364
|
-
|
409
|
+
recurrence_rule.until,
|
365
410
|
recurrence_rule.count,
|
366
411
|
recurrence_rule.interval.nil? ? 1 : recurrence_rule.interval,
|
367
412
|
excluding_dates,
|
368
413
|
overrides.keys
|
369
414
|
)
|
370
|
-
when
|
371
|
-
return name if
|
415
|
+
when 'WEEKLY'
|
416
|
+
return name if weekly_event_active_for_datetime?(
|
372
417
|
datetime,
|
373
418
|
event_start,
|
374
419
|
event_end,
|
375
|
-
|
420
|
+
recurrence_rule.until,
|
376
421
|
recurrence_rule.count,
|
377
422
|
recurrence_rule.interval.nil? ? 1 : recurrence_rule.interval,
|
378
423
|
recurrence_rule.by_day,
|
379
424
|
excluding_dates,
|
380
425
|
overrides.keys
|
381
426
|
)
|
382
|
-
when
|
383
|
-
return name if
|
427
|
+
when 'MONTHLY'
|
428
|
+
return name if monthly_event_active_for_datetime?(
|
384
429
|
datetime,
|
385
430
|
event_start,
|
386
431
|
event_end,
|
387
|
-
|
432
|
+
recurrence_rule.until,
|
388
433
|
recurrence_rule.count,
|
389
434
|
recurrence_rule.interval.nil? ? 1 : recurrence_rule.interval,
|
390
435
|
recurrence_rule.by_day,
|
@@ -392,19 +437,19 @@ module ActiveIcalendarEvents
|
|
392
437
|
excluding_dates,
|
393
438
|
overrides.keys
|
394
439
|
)
|
395
|
-
when
|
396
|
-
return name if
|
440
|
+
when 'YEARLY'
|
441
|
+
return name if yearly_event_active_for_datetime?(
|
397
442
|
datetime,
|
398
443
|
event_start,
|
399
444
|
event_end,
|
400
|
-
|
445
|
+
recurrence_rule.until,
|
401
446
|
recurrence_rule.count,
|
402
447
|
recurrence_rule.interval.nil? ? 1 : recurrence_rule.interval,
|
403
448
|
excluding_dates,
|
404
449
|
overrides.keys
|
405
450
|
)
|
406
451
|
else
|
407
|
-
throw RuntimeError,
|
452
|
+
throw RuntimeError, 'Invalid event frequency'
|
408
453
|
end
|
409
454
|
|
410
455
|
nil
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active-icalendar-events
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- William Starling
|
@@ -44,7 +44,7 @@ executables: []
|
|
44
44
|
extensions: []
|
45
45
|
extra_rdoc_files: []
|
46
46
|
files:
|
47
|
-
- lib/
|
47
|
+
- lib/active_icalendar_events.rb
|
48
48
|
homepage: https://github.com/foygl/active-icalendar-events
|
49
49
|
licenses:
|
50
50
|
- MIT
|