active-icalendar-events 0.1.2 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/active-icalendar-events.rb +149 -116
  3. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 981c99140882554d70998f2b1225ce89cb47b80fb7471172c62c215204aecdb5
4
- data.tar.gz: 5d58a68cb6195024aedb6a0d461c8c03a98a1f88ad5d4c0f0250e4fe08e5d077
3
+ metadata.gz: ee38fd75cc71289c8cf434fcf1d107f00619e3fd1b4556a775ea8c62d7ec129d
4
+ data.tar.gz: 64c6215c1072a70e6d67f50c5040c1e358a7320162c05ef0851a433b40b356b3
5
5
  SHA512:
6
- metadata.gz: 426bb70b73ab75023013c15040498a1fc69605c5c39f08d06a83457e8e29467074064c7058083dd7223d4afad2be5a3d5c281d8a289b79d36560eff124b57ad4
7
- data.tar.gz: be2600c740997701ffeab23b6eb51e844cefe31912f8c7baa9b383501a4505e50381e52c0fa844be76aad25ca2591403770999809ff0e7d0f88974f15b435083
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 { |e|
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 RuntimeError, 'Should only have one event that defines the recurrence in a group'
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 RuntimeError, 'Multiple recurrence rules not supported'
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
- :datetime => datetime,
67
- :name => r[:name],
68
- :event_start => r[:event_start],
69
- :event_end => r[:event_end],
70
- :recurrence_rule => r[:recurrence_rule].first,
71
- :recurrence_dates => r[:recurrence_dates],
72
- :excluding_dates => r[:excluding_dates],
73
- :overrides => events.reject { |e| e == r }.group_by { |e| e[:recurrence_id] }
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 RuntimeError, 'Not yet implemented when only recurrence_dates are provided'
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 { |e|
82
- active_events.add(e[:name]) if is_event_active?(datetime, e[:event_start].to_time, e[:event_end].to_time)
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 { |e|
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 = ActiveSupport::TimeZone.new(e.parent.timezones.first.tzid.to_s)
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 = ActiveSupport::TimeZone.new(e.parent.timezones.first.tzid.to_s)
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: e.rdate,
113
- excluding_dates: e.exdate,
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
- }.group_by { |e| e[:uid] }
151
+ end.group_by { |e| e[:uid] }
118
152
  end
119
153
 
120
- def is_event_active?(datetime, event_start, event_end)
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 is_daily_event_active_for_datetime?(datetime,
134
- event_start,
135
- event_end,
136
- until_datetime,
137
- count,
138
- interval,
139
- excluding_dates,
140
- overridden_dates)
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 is_event_active?(datetime, event_start_considered, event_end_considered)
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 = event_start_considered + interval.days
157
- event_end_considered = event_end_considered + interval.days
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 is_weekly_event_active_for_datetime?(datetime,
164
- event_start,
165
- event_end,
166
- until_datetime,
167
- count,
168
- interval,
169
- by_day,
170
- excluding_dates,
171
- overridden_dates)
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
- while !instance_count_exceeded?(considered_count, count)
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 is_event_active?(datetime, event_start_considered, event_end_considered)
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? ? event_start_considered :
193
- event_start_considered.prev_occurring(:monday)
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("%^a").chop
242
+ day_code = week_event_start_considered.strftime('%^a').chop
205
243
 
206
244
  if by_day.include?(day_code)
207
- if is_event_active?(datetime, week_event_start_considered, week_event_end_considered)
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 = week_event_start_considered + 1.days
218
- week_event_end_considered = week_event_end_considered + 1.days
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 = event_start_considered + interval.weeks
223
- event_end_considered = event_end_considered + interval.weeks
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 /^([0-9]+)([A-Z]+)$/
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 RuntimeError, "Unexpected day code used"
294
+ raise 'Unexpected day code used'
254
295
  end
255
296
 
256
- target_day = datetime.beginning_of_month
297
+ target_day = beginning_of_month(datetime)
257
298
 
258
- if target_day.strftime("%^a").chop != day_code
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 is_monthly_event_active_for_datetime?(datetime,
270
- event_start,
271
- event_end,
272
- until_datetime,
273
- count,
274
- interval,
275
- by_day,
276
- by_month_day,
277
- excluding_dates,
278
- overridden_dates)
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 is_event_active?(datetime, event_start_considered, event_end_considered)
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 = event_start_considered + interval.month
301
- event_end_considered = event_end_considered + interval.month
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.beginning_of_month + interval.month,
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 is_yearly_event_active_for_datetime?(datetime,
314
- event_start,
315
- event_end,
316
- until_datetime,
317
- count,
318
- interval,
319
- excluding_dates,
320
- overridden_dates)
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 is_event_active?(datetime, event_start_considered, event_end_considered)
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 = event_start_considered + interval.years
337
- event_end_considered = event_end_considered + interval.years
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 { |e|
353
- return e[:name] if e[:event_start] <= datetime.to_time &&
354
- e[:event_end] > datetime.to_time
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 { |recurrence_event_start|
361
- recurrence_event_end = recurrence_event_start + (event_end.to_time - event_start.to_time)
362
- return name if is_event_active?(datetime, recurrence_event_start, recurrence_event_end)
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 "DAILY"
372
- return name if is_daily_event_active_for_datetime?(
404
+ when 'DAILY'
405
+ return name if daily_event_active_for_datetime?(
373
406
  datetime,
374
407
  event_start,
375
408
  event_end,
376
- until_datetime,
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 "WEEKLY"
383
- return name if is_weekly_event_active_for_datetime?(
415
+ when 'WEEKLY'
416
+ return name if weekly_event_active_for_datetime?(
384
417
  datetime,
385
418
  event_start,
386
419
  event_end,
387
- until_datetime,
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 "MONTHLY"
395
- return name if is_monthly_event_active_for_datetime?(
427
+ when 'MONTHLY'
428
+ return name if monthly_event_active_for_datetime?(
396
429
  datetime,
397
430
  event_start,
398
431
  event_end,
399
- until_datetime,
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 "YEARLY"
408
- return name if is_yearly_event_active_for_datetime?(
440
+ when 'YEARLY'
441
+ return name if yearly_event_active_for_datetime?(
409
442
  datetime,
410
443
  event_start,
411
444
  event_end,
412
- until_datetime,
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, "Invalid event frequency"
452
+ throw RuntimeError, 'Invalid event frequency'
420
453
  end
421
454
 
422
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.1.2
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - William Starling