rubyredrick-ri_cal 0.0.2

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.
Files changed (123) hide show
  1. data/History.txt +3 -0
  2. data/Manifest.txt +122 -0
  3. data/README.txt +271 -0
  4. data/Rakefile +31 -0
  5. data/bin/ri_cal +8 -0
  6. data/component_attributes/alarm.yml +10 -0
  7. data/component_attributes/calendar.yml +4 -0
  8. data/component_attributes/component_property_defs.yml +180 -0
  9. data/component_attributes/event.yml +45 -0
  10. data/component_attributes/freebusy.yml +16 -0
  11. data/component_attributes/journal.yml +35 -0
  12. data/component_attributes/timezone.yml +3 -0
  13. data/component_attributes/timezone_period.yml +11 -0
  14. data/component_attributes/todo.yml +46 -0
  15. data/copyrights.txt +2 -0
  16. data/docs/draft-ietf-calsify-2446bis-08.txt +7280 -0
  17. data/docs/draft-ietf-calsify-rfc2445bis-09.txt +10416 -0
  18. data/docs/incrementers.txt +7 -0
  19. data/docs/rfc2445.pdf +0 -0
  20. data/lib/ri_cal/component/alarm.rb +22 -0
  21. data/lib/ri_cal/component/calendar.rb +199 -0
  22. data/lib/ri_cal/component/event.rb +30 -0
  23. data/lib/ri_cal/component/freebusy.rb +19 -0
  24. data/lib/ri_cal/component/journal.rb +22 -0
  25. data/lib/ri_cal/component/t_z_info_timezone.rb +124 -0
  26. data/lib/ri_cal/component/timezone/daylight_period.rb +26 -0
  27. data/lib/ri_cal/component/timezone/standard_period.rb +24 -0
  28. data/lib/ri_cal/component/timezone/timezone_period.rb +54 -0
  29. data/lib/ri_cal/component/timezone.rb +193 -0
  30. data/lib/ri_cal/component/todo.rb +26 -0
  31. data/lib/ri_cal/component.rb +224 -0
  32. data/lib/ri_cal/core_extensions/array/conversions.rb +15 -0
  33. data/lib/ri_cal/core_extensions/array.rb +7 -0
  34. data/lib/ri_cal/core_extensions/date/conversions.rb +27 -0
  35. data/lib/ri_cal/core_extensions/date.rb +13 -0
  36. data/lib/ri_cal/core_extensions/date_time/conversions.rb +27 -0
  37. data/lib/ri_cal/core_extensions/date_time.rb +13 -0
  38. data/lib/ri_cal/core_extensions/object/conversions.rb +20 -0
  39. data/lib/ri_cal/core_extensions/object.rb +8 -0
  40. data/lib/ri_cal/core_extensions/string/conversions.rb +32 -0
  41. data/lib/ri_cal/core_extensions/string.rb +8 -0
  42. data/lib/ri_cal/core_extensions/time/calculations.rb +153 -0
  43. data/lib/ri_cal/core_extensions/time/conversions.rb +27 -0
  44. data/lib/ri_cal/core_extensions/time/week_day_predicates.rb +88 -0
  45. data/lib/ri_cal/core_extensions/time.rb +11 -0
  46. data/lib/ri_cal/core_extensions.rb +6 -0
  47. data/lib/ri_cal/invalid_timezone_identifer.rb +20 -0
  48. data/lib/ri_cal/occurrence_enumerator.rb +172 -0
  49. data/lib/ri_cal/parser.rb +138 -0
  50. data/lib/ri_cal/properties/alarm.rb +390 -0
  51. data/lib/ri_cal/properties/calendar.rb +164 -0
  52. data/lib/ri_cal/properties/event.rb +1526 -0
  53. data/lib/ri_cal/properties/freebusy.rb +594 -0
  54. data/lib/ri_cal/properties/journal.rb +1240 -0
  55. data/lib/ri_cal/properties/timezone.rb +151 -0
  56. data/lib/ri_cal/properties/timezone_period.rb +416 -0
  57. data/lib/ri_cal/properties/todo.rb +1562 -0
  58. data/lib/ri_cal/property_value/array.rb +19 -0
  59. data/lib/ri_cal/property_value/cal_address.rb +12 -0
  60. data/lib/ri_cal/property_value/date.rb +119 -0
  61. data/lib/ri_cal/property_value/date_time/additive_methods.rb +43 -0
  62. data/lib/ri_cal/property_value/date_time/time_machine.rb +180 -0
  63. data/lib/ri_cal/property_value/date_time/timezone_support.rb +65 -0
  64. data/lib/ri_cal/property_value/date_time.rb +324 -0
  65. data/lib/ri_cal/property_value/duration.rb +106 -0
  66. data/lib/ri_cal/property_value/geo.rb +12 -0
  67. data/lib/ri_cal/property_value/integer.rb +13 -0
  68. data/lib/ri_cal/property_value/occurrence_list.rb +82 -0
  69. data/lib/ri_cal/property_value/period.rb +63 -0
  70. data/lib/ri_cal/property_value/recurrence_rule/enumeration_support_methods.rb +98 -0
  71. data/lib/ri_cal/property_value/recurrence_rule/enumerator.rb +77 -0
  72. data/lib/ri_cal/property_value/recurrence_rule/initialization_methods.rb +149 -0
  73. data/lib/ri_cal/property_value/recurrence_rule/negative_setpos_enumerator.rb +54 -0
  74. data/lib/ri_cal/property_value/recurrence_rule/numbered_span.rb +32 -0
  75. data/lib/ri_cal/property_value/recurrence_rule/occurence_incrementer.rb +794 -0
  76. data/lib/ri_cal/property_value/recurrence_rule/recurring_day.rb +132 -0
  77. data/lib/ri_cal/property_value/recurrence_rule/recurring_month_day.rb +61 -0
  78. data/lib/ri_cal/property_value/recurrence_rule/recurring_numbered_week.rb +34 -0
  79. data/lib/ri_cal/property_value/recurrence_rule/recurring_year_day.rb +50 -0
  80. data/lib/ri_cal/property_value/recurrence_rule/validations.rb +126 -0
  81. data/lib/ri_cal/property_value/recurrence_rule.rb +146 -0
  82. data/lib/ri_cal/property_value/text.rb +41 -0
  83. data/lib/ri_cal/property_value/uri.rb +12 -0
  84. data/lib/ri_cal/property_value/utc_offset.rb +34 -0
  85. data/lib/ri_cal/property_value.rb +110 -0
  86. data/lib/ri_cal/required_timezones.rb +56 -0
  87. data/lib/ri_cal/time_with_floating_timezone.rb +59 -0
  88. data/lib/ri_cal.rb +134 -0
  89. data/ri_cal.gemspec +47 -0
  90. data/sample_ical_files/from_ical_dot_app/test1.ics +38 -0
  91. data/script/console +10 -0
  92. data/script/destroy +14 -0
  93. data/script/generate +14 -0
  94. data/script/txt2html +71 -0
  95. data/spec/ri_cal/component/alarm_spec.rb +13 -0
  96. data/spec/ri_cal/component/calendar_spec.rb +55 -0
  97. data/spec/ri_cal/component/event_spec.rb +157 -0
  98. data/spec/ri_cal/component/freebusy_spec.rb +13 -0
  99. data/spec/ri_cal/component/journal_spec.rb +13 -0
  100. data/spec/ri_cal/component/t_z_info_timezone_spec.rb +37 -0
  101. data/spec/ri_cal/component/timezone_spec.rb +155 -0
  102. data/spec/ri_cal/component/todo_spec.rb +61 -0
  103. data/spec/ri_cal/component_spec.rb +212 -0
  104. data/spec/ri_cal/core_extensions/time/calculations_spec.rb +189 -0
  105. data/spec/ri_cal/core_extensions/time/week_day_predicates_spec.rb +46 -0
  106. data/spec/ri_cal/occurrence_enumerator_spec.rb +218 -0
  107. data/spec/ri_cal/parser_spec.rb +304 -0
  108. data/spec/ri_cal/property_value/date_spec.rb +22 -0
  109. data/spec/ri_cal/property_value/date_time_spec.rb +448 -0
  110. data/spec/ri_cal/property_value/duration_spec.rb +80 -0
  111. data/spec/ri_cal/property_value/period_spec.rb +50 -0
  112. data/spec/ri_cal/property_value/recurrence_rule/recurring_year_day_spec.rb +22 -0
  113. data/spec/ri_cal/property_value/recurrence_rule_spec.rb +1815 -0
  114. data/spec/ri_cal/property_value/text_spec.rb +14 -0
  115. data/spec/ri_cal/property_value/utc_offset_spec.rb +49 -0
  116. data/spec/ri_cal/property_value_spec.rb +111 -0
  117. data/spec/ri_cal/required_timezones_spec.rb +68 -0
  118. data/spec/ri_cal_spec.rb +54 -0
  119. data/spec/spec.opts +4 -0
  120. data/spec/spec_helper.rb +24 -0
  121. data/tasks/ri_cal.rake +403 -0
  122. data/tasks/spec.rake +35 -0
  123. metadata +201 -0
@@ -0,0 +1,794 @@
1
+ module RiCal
2
+ class PropertyValue
3
+ class RecurrenceRule < PropertyValue
4
+ module TimeManipulation #:nodoc:
5
+
6
+ def advance_day(date_time)
7
+ date_time.advance(:days => 1)
8
+ end
9
+
10
+ def first_hour_of_day(date_time)
11
+ date_time.change(:hour => 0)
12
+ end
13
+
14
+ def advance_week(date_time)
15
+ date_time.advance(:days => 7)
16
+ end
17
+
18
+ def first_day_of_week(wkst_day, date_time)
19
+ date_time.at_start_of_week_with_wkst(wkst_day)
20
+ end
21
+
22
+ def advance_month(date_time)
23
+ date_time.advance(:months => 1)
24
+ end
25
+
26
+ def first_day_of_month(date_time)
27
+ date_time.change(:day => 1)
28
+ end
29
+
30
+ def advance_year(date_time)
31
+ date_time.advance(:years => 1)
32
+ end
33
+
34
+ def first_day_of_year(date_time)
35
+ date_time.change(:month => 1, :day => 1)
36
+ end
37
+ end
38
+ #- ©2009 Rick DeNatale
39
+ #- All rights reserved. Refer to the file README.txt for the license
40
+ #
41
+ class OccurrenceIncrementer # :nodoc:
42
+
43
+ attr_accessor :sub_cycle_incrementer, :current_occurrence, :outer_range
44
+ attr_accessor :outer_incrementers
45
+ attr_accessor :contains_daily_incrementer, :contains_weeknum_incrementer
46
+ attr_reader :leaf_iterator
47
+
48
+ include TimeManipulation
49
+
50
+ class NullSubCycleIncrementer #:nodoc:
51
+ def self.next_time(previous)
52
+ nil
53
+ end
54
+
55
+ def self.add_outer_incrementer(incrementer)
56
+ end
57
+
58
+ def self.first_within_outer_cycle(previous_occurrence, outer_cycle_range)
59
+ outer_cycle_range.first
60
+ end
61
+
62
+ def self.first_sub_occurrence(previous_occurrence, outer_cycle_range)
63
+ nil
64
+ end
65
+
66
+ def self.cycle_adjust(date_time)
67
+ date_time
68
+ end
69
+
70
+ def self.to_s
71
+ "NULL-INCR"
72
+ end
73
+
74
+ def inspect
75
+ to_s
76
+ end
77
+ end
78
+
79
+ def initialize(rrule, sub_cycle_incrementer)
80
+ self.sub_cycle_incrementer = sub_cycle_incrementer
81
+ @outermost = true
82
+ self.outer_incrementers = []
83
+ if sub_cycle_incrementer
84
+ self.contains_daily_incrementer = sub_cycle_incrementer.daily_incrementer? ||
85
+ sub_cycle_incrementer.contains_daily_incrementer?
86
+ self.contains_weeknum_incrementer = sub_cycle_incrementer.weeknum_incrementer?||
87
+ sub_cycle_incrementer.contains_weeknum_incrementer?
88
+ sub_cycle_incrementer.add_outer_incrementer(self)
89
+ else
90
+ self.sub_cycle_incrementer = NullSubCycleIncrementer
91
+ end
92
+ end
93
+
94
+ def add_outer_incrementer(incrementer)
95
+ @outermost = false
96
+ self.outer_incrementers << incrementer
97
+ sub_cycle_incrementer.add_outer_incrementer(incrementer)
98
+ end
99
+
100
+ def outermost?
101
+ @outermost
102
+ end
103
+
104
+ def to_s
105
+ if sub_cycle_incrementer
106
+ "#{self.short_name}->#{sub_cycle_incrementer}"
107
+ else
108
+ self.short_name
109
+ end
110
+ end
111
+
112
+ def short_name
113
+ @short_name ||= self.class.name.split("::").last
114
+ end
115
+
116
+ # Return the next time after previous_occurrence generated by this incrementer
117
+ # But the occurrence is outside the current cycle of any outer incrementer(s) return
118
+ # nil which will cause the outer incrementer to step to its next cycle.
119
+ def next_time(previous_occurrence)
120
+ if current_occurrence
121
+ sub_occurrence = sub_cycle_incrementer.next_time(previous_occurrence)
122
+ else #first time
123
+ sub_occurrence = sub_cycle_incrementer.first_sub_occurrence(previous_occurrence, update_cycle_range(previous_occurrence))
124
+ end
125
+ if sub_occurrence
126
+ candidate = sub_occurrence
127
+ else
128
+ candidate = next_cycle(previous_occurrence)
129
+ end
130
+ if in_outer_cycle?(candidate)
131
+ candidate
132
+ else
133
+ nil
134
+ end
135
+ end
136
+
137
+ def update_cycle_range(date_time)
138
+ self.current_occurrence = date_time
139
+ (date_time..end_of_occurrence(date_time))
140
+ end
141
+
142
+ def in_outer_cycle?(candidate)
143
+ candidate && (outer_range.nil? || (outer_range.first <= candidate && outer_range.last >= candidate))
144
+ end
145
+
146
+ def first_sub_occurrence(previous_occurrence, outer_cycle_range)
147
+ first_within_outer_cycle(previous_occurrence, outer_cycle_range)
148
+ end
149
+
150
+ # Advance to the next cycle, if the result is within the current cycles of all outer incrementers
151
+ def next_cycle(previous_occurrence)
152
+ raise "next_cycle is a subclass responsibility"
153
+ end
154
+
155
+ def contains_daily_incrementer?
156
+ @contains_daily_incrementer
157
+ end
158
+
159
+ def daily_incrementer?
160
+ false
161
+ end
162
+
163
+ def contains_weeknum_incrementer?
164
+ @contains_weeknum_incrementer
165
+ end
166
+
167
+ def weeknum_incrementer?
168
+ false
169
+ end
170
+ end
171
+
172
+ # A ListIncrementer represents a byxxx part of a recurrence rule
173
+ # It contains a list of simple values or recurring values
174
+ # It keeps a collection of occurrences within a given range called a cycle
175
+ # When the collection of occurrences is exhausted it is refreshed if there is no
176
+ # outer incrementer, or if a new cycle would start in the current cycle of the outer incrementers.
177
+ class ListIncrementer < OccurrenceIncrementer #:nodoc:
178
+ attr_accessor :occurrences, :list, :outer_occurrence, :cycle_start
179
+
180
+ def initialize(rrule, list, sub_cycle_incrementer)
181
+ super(rrule, sub_cycle_incrementer)
182
+ self.list = list
183
+ end
184
+
185
+ def self.conditional_incrementer(rrule, by_part, sub_cycle_class)
186
+ sub_cycle_incrementer = sub_cycle_class.for_rrule(rrule)
187
+ list = rrule.by_rule_list(by_part)
188
+ if list
189
+ new(rrule, list, sub_cycle_incrementer)
190
+ else
191
+ sub_cycle_incrementer
192
+ end
193
+ end
194
+
195
+ # Advance to the next occurrence, if the result is within the current cycles of all outer incrementers
196
+ def next_cycle(previous_occurrence)
197
+ unless occurrences
198
+ self.occurrences = occurrences_for(previous_occurrence)
199
+ end
200
+ candidate = next_candidate(previous_occurrence)
201
+ if candidate
202
+ sub_cycle_incrementer.first_within_outer_cycle(previous_occurrence, update_cycle_range(candidate))
203
+ else
204
+ nil
205
+ end
206
+ end
207
+
208
+ def first_within_outer_cycle(previous_occurrence, outer_range)
209
+ self.outer_range = outer_range
210
+ self.occurrences = occurrences_within(outer_range)
211
+ occurrences.each { |occurrence|
212
+ sub = sub_cycle_incrementer.first_within_outer_cycle(previous_occurrence, update_cycle_range(occurrence))
213
+ return sub if sub && sub > previous_occurrence
214
+ }
215
+ nil
216
+ end
217
+
218
+ def next_candidate(date_time)
219
+ candidate = next_in_list(date_time)
220
+ if outermost?
221
+ while candidate.nil?
222
+ get_next_occurrences
223
+ candidate = next_in_list(date_time)
224
+ end
225
+ end
226
+ candidate
227
+ end
228
+
229
+ def next_in_list(date_time)
230
+ occurrences.find {|occurrence| occurrence > date_time}
231
+ end
232
+
233
+ def get_next_occurrences
234
+ adv_cycle = advance_cycle(start_of_cycle(occurrences.first))
235
+ self.occurrences = occurrences_for(adv_cycle)
236
+ end
237
+
238
+ def cycle_adjust(date_time)
239
+ sub_cycle_incrementer.cycle_adjust(start_of_cycle(date_time))
240
+ end
241
+
242
+ def occurrences_for(date_time)
243
+ list.map {|value| date_time.change(varying_time_attribute => value)}
244
+ end
245
+
246
+ def occurrences_within(date_time_range)
247
+ result = []
248
+ date_time = date_time_range.first
249
+ while date_time <= date_time_range.last
250
+ result << occurrences_for(date_time)
251
+ date_time = advance_cycle(date_time)
252
+ end
253
+ result.flatten
254
+ end
255
+ end
256
+
257
+ # A FrequenceIncrementer represents the xxxLY and FREQ parts of a recurrence rule
258
+ # A FrequenceIncrementer has a single occurrence within each cycle.
259
+ class FrequencyIncrementer < OccurrenceIncrementer #:nodoc:
260
+ attr_accessor :interval, :outer_occurrence, :skip_increment
261
+
262
+ alias_method :cycle_start, :current_occurrence
263
+
264
+ def initialize(rrule, sub_cycle_incrementer)
265
+ super(rrule, sub_cycle_incrementer)
266
+ self.interval = rrule.interval
267
+ end
268
+
269
+ def self.conditional_incrementer(rrule, freq_str, sub_cycle_class)
270
+ sub_cycle_incrementer = sub_cycle_class.for_rrule(rrule)
271
+ if rrule.freq == freq_str
272
+ new(rrule, sub_cycle_incrementer)
273
+ else
274
+ sub_cycle_incrementer
275
+ end
276
+ end
277
+
278
+ def multiplier
279
+ 1
280
+ end
281
+
282
+ def step(occurrence)
283
+ occurrence.advance(advance_what => (interval * multiplier))
284
+ end
285
+
286
+ def first_within_outer_cycle(previous_occurrence, outer_cycle_range)
287
+ if outer_range
288
+ first_occurrence = outer_cycle_range.first
289
+ else
290
+ first_occurrence = step(previous_occurrence)
291
+ end
292
+ self.outer_range = outer_cycle_range
293
+ sub_cycle_incrementer.first_within_outer_cycle(previous_occurrence, update_cycle_range(first_occurrence))
294
+ end
295
+
296
+ # Advance to the next occurrence, if the result is within the current cycles of all outer incrementers
297
+ def next_cycle(previous_occurrence)
298
+ if current_occurrence
299
+ candidate = sub_cycle_incrementer.cycle_adjust(step(current_occurrence))
300
+ else
301
+ candidate = step(previous_occurrence)
302
+ end
303
+ if outermost?
304
+ sub_occurrence = sub_cycle_incrementer.first_within_outer_cycle(previous_occurrence, update_cycle_range(candidate))
305
+ until sub_occurrence
306
+ candidate = sub_cycle_incrementer.cycle_adjust(step(candidate))
307
+ sub_occurrence = sub_cycle_incrementer.first_within_outer_cycle(previous_occurrence, update_cycle_range(candidate))
308
+ end
309
+ sub_occurrence
310
+ elsif in_outer_cycle?(candidate)
311
+ sub_cycle_incrementer.first_within_outer_cycle(previous_occurrence, update_cycle_range(candidate))
312
+ else
313
+ nil
314
+ end
315
+ end
316
+ end
317
+
318
+ class SecondlyIncrementer < FrequencyIncrementer #:nodoc:
319
+
320
+ def self.for_rrule(rrule)
321
+ if rrule.freq == "SECONDLY"
322
+ new(rrule, nil)
323
+ else
324
+ nil
325
+ end
326
+ end
327
+
328
+ def advance_what
329
+ :seconds
330
+ end
331
+
332
+ def end_of_occurrence(date_time)
333
+ date_time
334
+ end
335
+ end
336
+
337
+
338
+ class BySecondIncrementer < ListIncrementer #:nodoc:
339
+
340
+ def self.for_rrule(rrule)
341
+ conditional_incrementer(rrule, :bysecond, SecondlyIncrementer)
342
+ end
343
+
344
+ def varying_time_attribute
345
+ :sec
346
+ end
347
+
348
+ def start_of_cycle(date_time)
349
+ date_time.start_of_minute
350
+ end
351
+
352
+ def advance_cycle(date_time)
353
+ date_time.advance(:minutes => 1).start_of_minute
354
+ end
355
+
356
+ def end_of_occurrence(date_time)
357
+ date_time
358
+ end
359
+ end
360
+
361
+ class MinutelyIncrementer < FrequencyIncrementer #:nodoc:
362
+ def self.for_rrule(rrule)
363
+ conditional_incrementer(rrule, "MINUTELY", BySecondIncrementer)
364
+ end
365
+
366
+ def advance_what
367
+ :minutes
368
+ end
369
+
370
+ def end_of_occurrence(date_time)
371
+ date_time.end_of_minute
372
+ end
373
+ end
374
+
375
+ class ByMinuteIncrementer < ListIncrementer #:nodoc:
376
+ def self.for_rrule(rrule)
377
+ conditional_incrementer(rrule, :byminute, MinutelyIncrementer)
378
+ end
379
+
380
+ def advance_cycle(date_time)
381
+ date_time.advance(:hours => 1).start_of_hour
382
+ end
383
+
384
+ def start_of_cycle(date_time)
385
+ date_time.change(:min => 0)
386
+ end
387
+
388
+ def end_of_occurrence(date_time)
389
+ date_time.end_of_minute
390
+ end
391
+
392
+ def varying_time_attribute
393
+ :min
394
+ end
395
+ end
396
+
397
+ class HourlyIncrementer < FrequencyIncrementer #:nodoc:
398
+ def self.for_rrule(rrule)
399
+ conditional_incrementer(rrule, "HOURLY", ByMinuteIncrementer)
400
+ end
401
+
402
+ def advance_what
403
+ :hours
404
+ end
405
+
406
+ def end_of_occurrence(date_time)
407
+ date_time.end_of_hour
408
+ end
409
+ end
410
+
411
+ class ByHourIncrementer < ListIncrementer #:nodoc:
412
+ def self.for_rrule(rrule)
413
+ conditional_incrementer(rrule, :byhour, HourlyIncrementer)
414
+ end
415
+
416
+ def start_of_cycle(date_time)
417
+ date_time.change(:hour => 0)
418
+ end
419
+
420
+ def varying_time_attribute
421
+ :hour
422
+ end
423
+
424
+ def advance_cycle(date_time)
425
+ first_hour_of_day(advance_day(date_time))
426
+ end
427
+
428
+ def end_of_occurrence(date_time)
429
+ date_time.end_of_hour
430
+ end
431
+ end
432
+
433
+ class DailyIncrementer < FrequencyIncrementer #:nodoc:
434
+
435
+ def self.for_rrule(rrule)
436
+ conditional_incrementer(rrule, "DAILY", ByHourIncrementer)
437
+ end
438
+
439
+ def daily_incrementer?
440
+ true
441
+ end
442
+
443
+ def advance_what
444
+ :days
445
+ end
446
+
447
+ def end_of_occurrence(date_time)
448
+ date_time.end_of_day
449
+ end
450
+ end
451
+
452
+ class ByNumberedDayIncrementer < ListIncrementer #:nodoc:
453
+
454
+ def daily_incrementer?
455
+ true
456
+ end
457
+
458
+ def occurrences_for(date_time)
459
+ if occurrences && @scoping_value == scope_of(date_time)
460
+ occurrences
461
+ else
462
+ @scoping_value = scope_of(date_time)
463
+ self.occurrences = list.map {|numbered_day| numbered_day.target_date_time_for(date_time)}.uniq.sort
464
+ occurrences
465
+ end
466
+ end
467
+
468
+ def end_of_occurrence(date_time)
469
+ date_time.end_of_day
470
+ end
471
+
472
+ def candidate_acceptible?(candidate)
473
+ list.any? {|by_part| by_part.include?(candidate)}
474
+ end
475
+ end
476
+
477
+ class ByMonthdayIncrementer < ByNumberedDayIncrementer #:nodoc:
478
+ def self.for_rrule(rrule)
479
+ conditional_incrementer(rrule, :bymonthday, DailyIncrementer)
480
+ end
481
+
482
+ def scope_of(date_time)
483
+ date_time.month
484
+ end
485
+
486
+ def start_of_cycle(date_time)
487
+ date_time.change(:day => 1)
488
+ end
489
+
490
+ def advance_cycle(date_time)
491
+ first_day_of_month(advance_month(date_time))
492
+ end
493
+
494
+ def end_of_occurrence(date_time)
495
+ date_time.end_of_day
496
+ end
497
+ end
498
+
499
+ class ByYeardayIncrementer < ByNumberedDayIncrementer #:nodoc:
500
+ def self.for_rrule(rrule)
501
+ conditional_incrementer(rrule, :byyearday, ByMonthdayIncrementer)
502
+ end
503
+
504
+ def start_of_cycle(date_time)
505
+ date_time.change(:month => 1, :day => 1)
506
+ end
507
+
508
+ def scope_of(date_time)
509
+ date_time.year
510
+ end
511
+
512
+ def advance_cycle(date_time)
513
+ first_day_of_year(advance_year(date_time))
514
+ end
515
+
516
+ def end_of_occurrence(date_time)
517
+ date_time.end_of_day
518
+ end
519
+ end
520
+
521
+ class ByDayIncrementer < ListIncrementer #:nodoc:
522
+
523
+ def initialize(rrule, list, by_monthday_list, by_yearday_list, parent)
524
+ super(rrule, list, parent)
525
+ @monthday_filters = by_monthday_list
526
+ @yearday_filters = by_yearday_list
527
+ @by_day_scope = rrule.by_day_scope
528
+
529
+ case rrule.by_day_scope
530
+ when :yearly
531
+ @cycle_advance_proc = lambda {|date_time| first_day_of_year(advance_year(date_time))}
532
+ @current_proc = lambda {|date_time| same_year?(current, date_time)}
533
+ @first_day_proc = lambda {|date_time| first_day_of_year(date_time)}
534
+ when :monthly
535
+ @cycle_advance_proc = lambda {|date_time| first_day_of_month(advance_month(date_time))}
536
+ @current_proc = lambda {|date_time| same_month?(current, date_time)}
537
+ @first_day_proc = lambda {|date_time| first_day_of_month(date_time)}
538
+ when :weekly
539
+ @cycle_advance_proc = lambda {|date_time| first_day_of_week(rrule.wkst_day, advance_week(date_time))}
540
+ @current_proc = lambda {|date_time| same_week?(rrule.wkst_day, current, date_time)}
541
+ @first_day_proc = lambda {|date_time| first_day_of_week(rrule.wkst_day, date_time)}
542
+ else
543
+ raise "Invalid recurrence rule, byday needs to be scoped by month, week or year"
544
+ end
545
+ end
546
+
547
+ def self.for_rrule(rrule)
548
+ list = rrule.by_rule_list(:byday)
549
+ if list
550
+ sub_cycle_incrementer = DailyIncrementer.for_rrule(rrule)
551
+ new(rrule, list, rrule.by_rule_list(:bymonthday), rrule.by_rule_list(:byyearday), sub_cycle_incrementer)
552
+ else
553
+ ByYeardayIncrementer.for_rrule(rrule)
554
+ end
555
+ end
556
+
557
+ def daily_incrementer?
558
+ true
559
+ end
560
+
561
+ def start_of_cycle(date_time)
562
+ @first_day_proc.call(date_time)
563
+ end
564
+
565
+ def occurrences_for(date_time)
566
+ first_day = start_of_cycle(date_time)
567
+ result = list.map {|recurring_day| recurring_day.matches_for(first_day)}.flatten.uniq.sort
568
+ if @monthday_filters
569
+ result = result.select {|occurrence| @monthday_filters.any? {|recurring_day| recurring_day.include?(occurrence)}}
570
+ end
571
+ if @yearday_filters
572
+ result = result.select {|occurrence| @yearday_filters.any? {|recurring_day| recurring_day.include?(occurrence)}}
573
+ end
574
+ result
575
+ end
576
+
577
+ def candidate_acceptible?(candidate)
578
+ list.any? {|recurring_day| recurring_day.include?(candidate)}
579
+ end
580
+
581
+ def varying_time_attribute
582
+ :day
583
+ end
584
+
585
+ def advance_cycle(date_time)
586
+ @cycle_advance_proc.call(date_time)
587
+ end
588
+
589
+ def end_of_occurrence(date_time)
590
+ date_time.end_of_day
591
+ end
592
+ end
593
+
594
+ class WeeklyIncrementer < FrequencyIncrementer #:nodoc:
595
+
596
+ attr_reader :wkst
597
+
598
+ # include WeeklyBydayMethods
599
+
600
+ def initialize(rrule, parent)
601
+ @wkst = rrule.wkst_day
602
+ super(rrule, parent)
603
+ end
604
+
605
+ def self.for_rrule(rrule)
606
+ conditional_incrementer(rrule, "WEEKLY", ByDayIncrementer)
607
+ end
608
+
609
+ def multiplier
610
+ 7
611
+ end
612
+
613
+ def advance_what
614
+ :days
615
+ end
616
+
617
+ def end_of_occurrence(date_time)
618
+ date_time.end_of_week_with_wkst(wkst)
619
+ end
620
+ end
621
+
622
+ class ByWeekNoIncrementer < ListIncrementer #:nodoc:
623
+ attr_reader :wkst
624
+ # include WeeklyBydayMethods
625
+
626
+ def initialize(rrule, list, sub_cycle_incrementer)
627
+ @wkst = rrule.wkst_day
628
+ super(rrule, list, sub_cycle_incrementer)
629
+ end
630
+
631
+ def self.for_rrule(rrule)
632
+ conditional_incrementer(rrule, :byweekno, WeeklyIncrementer)
633
+ end
634
+
635
+ def weeknum_incrementer?
636
+ true
637
+ end
638
+
639
+ def first_within_outer_cycle(previous_occurrence, outer_range)
640
+ new_range_start = outer_range.first
641
+ new_range_end = new_range_start.end_of_iso_year(wkst)
642
+ super(previous_occurrence, outer_range.first..new_range_end)
643
+ end
644
+
645
+ def start_of_cycle(date_time)
646
+ result = date_time.at_start_of_iso_year(wkst)
647
+ result
648
+ end
649
+
650
+ def occurrences_for(date_time)
651
+ iso_year, year_start = *date_time.iso_year_and_week_one_start(wkst)
652
+ week_one_occurrence = date_time.change(
653
+ :year => year_start.year,
654
+ :month => year_start.month,
655
+ :day => year_start.day
656
+ )
657
+ weeks_in_year_plus_one = week_one_occurrence.iso_weeks_in_year(wkst)
658
+ weeks = list.map {|recurring_weeknum|
659
+ wk_num = recurring_weeknum.ordinal
660
+ (wk_num > 0) ? wk_num : weeks_in_year_plus_one + wk_num
661
+ }.uniq.sort
662
+ weeks.map {|wk_num| week_one_occurrence.advance(:days => (wk_num - 1) * 7)}
663
+ end
664
+
665
+ def candidate_acceptible?(candidate)
666
+ list.include?(candidate.iso_week_num(wkst))
667
+ end
668
+
669
+ def advance_cycle(date_time)
670
+ date_time.at_start_of_next_iso_year(wkst)
671
+ end
672
+
673
+ def end_of_occurrence(date_time)
674
+ date_time.end_of_week_with_wkst(wkst)
675
+ end
676
+ end
677
+
678
+ class MonthlyIncrementer < FrequencyIncrementer #:nodoc:
679
+
680
+ def self.for_rrule(rrule)
681
+ conditional_incrementer(rrule, "MONTHLY", ByWeekNoIncrementer)
682
+ end
683
+
684
+ def advance_what
685
+ :months
686
+ end
687
+
688
+ def step(date_time)
689
+ if contains_daily_incrementer?
690
+ result = super(date_time).change(:day => 1)
691
+ result
692
+ else
693
+ super(date_time)
694
+ end
695
+ end
696
+
697
+ def end_of_occurrence(date_time)
698
+ date_time.end_of_month
699
+ end
700
+ end
701
+
702
+ class ByMonthIncrementer < ListIncrementer #:nodoc:
703
+
704
+ def self.for_rrule(rrule)
705
+ conditional_incrementer(rrule, :bymonth, MonthlyIncrementer)
706
+ end
707
+
708
+ def occurrences_for(date_time)
709
+ if contains_daily_incrementer?
710
+ list.map {|value| date_time.change(:month => value, :day => 1)}
711
+ else
712
+ list.map {|value| date_time.in_month(value)}
713
+ end
714
+ end
715
+
716
+ def range_advance(date_time)
717
+ advance_year(date_time)
718
+ end
719
+
720
+ def start_of_cycle(date_time)
721
+ if contains_daily_incrementer?
722
+ date_time.change(:month => 1, :day => 1)
723
+ else
724
+ date_time.change(:month => 1)
725
+ end
726
+ end
727
+
728
+ def varying_time_attribute
729
+ :month
730
+ end
731
+
732
+ def advance_cycle(date_time)
733
+ if contains_daily_incrementer?
734
+ first_day_of_year(advance_year(date_time))
735
+ else
736
+ advance_year(date_time).change(:month => 1)
737
+ end
738
+ end
739
+
740
+ def end_of_occurrence(date_time)
741
+ date_time.end_of_month
742
+ end
743
+ end
744
+
745
+ class YearlyIncrementer < FrequencyIncrementer #:nodoc:
746
+
747
+ attr_reader :wkst
748
+
749
+ def initialize(rrule, sub_cycle_incrementer)
750
+ @wkst = rrule.wkst_day
751
+ super(rrule, sub_cycle_incrementer)
752
+ end
753
+
754
+ def self.from_rrule(rrule, start_time)
755
+ conditional_incrementer(rrule, "YEARLY", ByMonthIncrementer)
756
+ end
757
+
758
+ def advance_what
759
+ :years
760
+ end
761
+
762
+ def step(date_time)
763
+ if contains_weeknum_incrementer?
764
+ result = date_time
765
+ multiplier.times do
766
+ result = result.at_start_of_next_iso_year(wkst)
767
+ end
768
+ result
769
+ else
770
+ super(date_time)
771
+ end
772
+ end
773
+
774
+ def start_of_cycle(date_time)
775
+ if contains_weeknum_incrementer?
776
+ date_time.at_start_of_iso_year(wkst)
777
+ elsif contains_daily_incrementer?
778
+ date_time.change(:month => 1, :day => 1)
779
+ else
780
+ date_time.change(:month => 1)
781
+ end
782
+ end
783
+
784
+ def end_of_occurrence(date_time)
785
+ if contains_weeknum_incrementer?
786
+ date_time.end_of_iso_year(wkst)
787
+ else
788
+ date_time.end_of_year
789
+ end
790
+ end
791
+ end
792
+ end
793
+ end
794
+ end