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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6d892e9c6fe0c2ed7de91e9f085aa7e812901b6ddce53184928d6be5482a2a3b
4
- data.tar.gz: 3ab320fe7a381d9361a50a629eeafdcae8e581b7695cdbc514b140e77fb11171
3
+ metadata.gz: 2c58972dd8113dffe13dc9434ac99480bf4bbffcc6013e8bbc082a38e0487729
4
+ data.tar.gz: 29e515e96bad5067907bcf7b43bf375ce2a01df43de0d743f7bb31c52566f483
5
5
  SHA512:
6
- metadata.gz: 8a702e4fdec5d0474469d5445ab6eb218a54cebfce69d40872c7c75e5a3328666e1e6e917e6073ea379effc52bc6fc660c662e09442f332d1cf955997b23d3d8
7
- data.tar.gz: 5956bb66a7955f25126cd861fb77e0386d8ee2cd073c7d36468007a4879185b4b0b8ce9599f2dbc62e87516bdbb2263e36ed706c68afc397c5a2145757bd11ce
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 { |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,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 { |e|
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: e.dtstart,
98
- event_end: e.dtend,
143
+ event_start: event_start,
144
+ event_end: event_end,
99
145
  recurrence_rule: e.rrule,
100
- recurrence_dates: e.rdate,
101
- excluding_dates: e.exdate,
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
- }.group_by { |e| e[:uid] }
151
+ end.group_by { |e| e[:uid] }
106
152
  end
107
153
 
108
- def is_event_active?(datetime, event_start, event_end)
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 is_daily_event_active_for_datetime?(datetime,
122
- event_start,
123
- event_end,
124
- until_datetime,
125
- count,
126
- interval,
127
- excluding_dates,
128
- 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)
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 is_event_active?(datetime, event_start_considered, event_end_considered)
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 = event_start_considered + interval.days
145
- event_end_considered = event_end_considered + interval.days
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 is_weekly_event_active_for_datetime?(datetime,
152
- event_start,
153
- event_end,
154
- until_datetime,
155
- count,
156
- interval,
157
- by_day,
158
- excluding_dates,
159
- 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)
160
206
  event_start_considered = event_start
161
207
  event_end_considered = event_end
162
208
  considered_count = 1
163
- while !instance_count_exceeded?(considered_count, count)
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 is_event_active?(datetime, event_start_considered, event_end_considered)
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? ? event_start_considered :
181
- 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
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("%^a").chop
242
+ day_code = week_event_start_considered.strftime('%^a').chop
193
243
 
194
244
  if by_day.include?(day_code)
195
- 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)
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 = week_event_start_considered + 1.days
206
- week_event_end_considered = week_event_end_considered + 1.days
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 = event_start_considered + interval.weeks
211
- event_end_considered = event_end_considered + interval.weeks
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 /^([0-9]+)([A-Z]+)$/
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 RuntimeError, "Unexpected day code used"
294
+ raise 'Unexpected day code used'
242
295
  end
243
296
 
244
- target_day = datetime.beginning_of_month
297
+ target_day = beginning_of_month(datetime)
245
298
 
246
- if target_day.strftime("%^a").chop != day_code
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 is_monthly_event_active_for_datetime?(datetime,
258
- event_start,
259
- event_end,
260
- until_datetime,
261
- count,
262
- interval,
263
- by_day,
264
- by_month_day,
265
- excluding_dates,
266
- 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)
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 is_event_active?(datetime, event_start_considered, event_end_considered)
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 = event_start_considered + interval.month
289
- event_end_considered = event_end_considered + interval.month
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.beginning_of_month + interval.month,
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 is_yearly_event_active_for_datetime?(datetime,
302
- event_start,
303
- event_end,
304
- until_datetime,
305
- count,
306
- interval,
307
- excluding_dates,
308
- 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)
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 is_event_active?(datetime, event_start_considered, event_end_considered)
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 = event_start_considered + interval.years
325
- event_end_considered = event_end_considered + interval.years
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 { |e|
341
- return e[:name] if e[:event_start] <= datetime.to_time &&
342
- e[:event_end] > datetime.to_time
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 { |recurrence_event_start|
349
- recurrence_event_end = recurrence_event_start + (event_end.to_time - event_start.to_time)
350
- return name if is_event_active?(datetime, recurrence_event_start, recurrence_event_end)
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 "DAILY"
360
- return name if is_daily_event_active_for_datetime?(
404
+ when 'DAILY'
405
+ return name if daily_event_active_for_datetime?(
361
406
  datetime,
362
407
  event_start,
363
408
  event_end,
364
- until_datetime,
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 "WEEKLY"
371
- return name if is_weekly_event_active_for_datetime?(
415
+ when 'WEEKLY'
416
+ return name if weekly_event_active_for_datetime?(
372
417
  datetime,
373
418
  event_start,
374
419
  event_end,
375
- until_datetime,
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 "MONTHLY"
383
- return name if is_monthly_event_active_for_datetime?(
427
+ when 'MONTHLY'
428
+ return name if monthly_event_active_for_datetime?(
384
429
  datetime,
385
430
  event_start,
386
431
  event_end,
387
- until_datetime,
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 "YEARLY"
396
- return name if is_yearly_event_active_for_datetime?(
440
+ when 'YEARLY'
441
+ return name if yearly_event_active_for_datetime?(
397
442
  datetime,
398
443
  event_start,
399
444
  event_end,
400
- until_datetime,
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, "Invalid event frequency"
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.1.1
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/active-icalendar-events.rb
47
+ - lib/active_icalendar_events.rb
48
48
  homepage: https://github.com/foygl/active-icalendar-events
49
49
  licenses:
50
50
  - MIT