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