active-icalendar-events 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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