demingfactor-ri_cal 0.9.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.
Files changed (167) hide show
  1. checksums.yaml +7 -0
  2. data/History.txt +402 -0
  3. data/Manifest.txt +161 -0
  4. data/README.txt +410 -0
  5. data/Rakefile +69 -0
  6. data/VERSION +1 -0
  7. data/bin/ri_cal +8 -0
  8. data/component_attributes/alarm.yml +10 -0
  9. data/component_attributes/calendar.yml +4 -0
  10. data/component_attributes/component_property_defs.yml +180 -0
  11. data/component_attributes/event.yml +45 -0
  12. data/component_attributes/freebusy.yml +16 -0
  13. data/component_attributes/journal.yml +35 -0
  14. data/component_attributes/timezone.yml +3 -0
  15. data/component_attributes/timezone_period.yml +11 -0
  16. data/component_attributes/todo.yml +46 -0
  17. data/copyrights.txt +1 -0
  18. data/docs/draft-ietf-calsify-2446bis-08.txt +7280 -0
  19. data/docs/draft-ietf-calsify-rfc2445bis-09.txt +10416 -0
  20. data/docs/incrementers.txt +7 -0
  21. data/docs/rfc2445.pdf +0 -0
  22. data/lib/ri_cal/component/alarm.rb +19 -0
  23. data/lib/ri_cal/component/calendar.rb +257 -0
  24. data/lib/ri_cal/component/event.rb +58 -0
  25. data/lib/ri_cal/component/freebusy.rb +16 -0
  26. data/lib/ri_cal/component/journal.rb +27 -0
  27. data/lib/ri_cal/component/non_standard.rb +33 -0
  28. data/lib/ri_cal/component/t_z_info_timezone.rb +153 -0
  29. data/lib/ri_cal/component/timezone/daylight_period.rb +25 -0
  30. data/lib/ri_cal/component/timezone/standard_period.rb +23 -0
  31. data/lib/ri_cal/component/timezone/timezone_period.rb +76 -0
  32. data/lib/ri_cal/component/timezone.rb +197 -0
  33. data/lib/ri_cal/component/todo.rb +42 -0
  34. data/lib/ri_cal/component.rb +256 -0
  35. data/lib/ri_cal/core_extensions/array/conversions.rb +15 -0
  36. data/lib/ri_cal/core_extensions/array.rb +7 -0
  37. data/lib/ri_cal/core_extensions/date/conversions.rb +56 -0
  38. data/lib/ri_cal/core_extensions/date.rb +13 -0
  39. data/lib/ri_cal/core_extensions/date_time/conversions.rb +50 -0
  40. data/lib/ri_cal/core_extensions/date_time.rb +15 -0
  41. data/lib/ri_cal/core_extensions/object/conversions.rb +20 -0
  42. data/lib/ri_cal/core_extensions/object.rb +8 -0
  43. data/lib/ri_cal/core_extensions/string/conversions.rb +57 -0
  44. data/lib/ri_cal/core_extensions/string.rb +8 -0
  45. data/lib/ri_cal/core_extensions/time/calculations.rb +153 -0
  46. data/lib/ri_cal/core_extensions/time/conversions.rb +42 -0
  47. data/lib/ri_cal/core_extensions/time/tzid_access.rb +50 -0
  48. data/lib/ri_cal/core_extensions/time/week_day_predicates.rb +55 -0
  49. data/lib/ri_cal/core_extensions/time.rb +14 -0
  50. data/lib/ri_cal/core_extensions.rb +11 -0
  51. data/lib/ri_cal/fast_date_time.rb +234 -0
  52. data/lib/ri_cal/floating_timezone.rb +32 -0
  53. data/lib/ri_cal/invalid_property_value.rb +8 -0
  54. data/lib/ri_cal/invalid_timezone_identifier.rb +20 -0
  55. data/lib/ri_cal/occurrence_enumerator.rb +265 -0
  56. data/lib/ri_cal/occurrence_period.rb +17 -0
  57. data/lib/ri_cal/parser.rb +148 -0
  58. data/lib/ri_cal/properties/alarm.rb +390 -0
  59. data/lib/ri_cal/properties/calendar.rb +164 -0
  60. data/lib/ri_cal/properties/event.rb +1523 -0
  61. data/lib/ri_cal/properties/freebusy.rb +593 -0
  62. data/lib/ri_cal/properties/journal.rb +1237 -0
  63. data/lib/ri_cal/properties/timezone.rb +150 -0
  64. data/lib/ri_cal/properties/timezone_period.rb +416 -0
  65. data/lib/ri_cal/properties/todo.rb +1559 -0
  66. data/lib/ri_cal/properties.rb +12 -0
  67. data/lib/ri_cal/property_value/array.rb +27 -0
  68. data/lib/ri_cal/property_value/cal_address.rb +11 -0
  69. data/lib/ri_cal/property_value/date.rb +184 -0
  70. data/lib/ri_cal/property_value/date_time/additive_methods.rb +44 -0
  71. data/lib/ri_cal/property_value/date_time/time_machine.rb +159 -0
  72. data/lib/ri_cal/property_value/date_time/timezone_support.rb +100 -0
  73. data/lib/ri_cal/property_value/date_time.rb +359 -0
  74. data/lib/ri_cal/property_value/duration.rb +110 -0
  75. data/lib/ri_cal/property_value/geo.rb +11 -0
  76. data/lib/ri_cal/property_value/integer.rb +12 -0
  77. data/lib/ri_cal/property_value/occurrence_list.rb +144 -0
  78. data/lib/ri_cal/property_value/period.rb +86 -0
  79. data/lib/ri_cal/property_value/recurrence_rule/enumeration_support_methods.rb +100 -0
  80. data/lib/ri_cal/property_value/recurrence_rule/enumerator.rb +79 -0
  81. data/lib/ri_cal/property_value/recurrence_rule/initialization_methods.rb +148 -0
  82. data/lib/ri_cal/property_value/recurrence_rule/negative_setpos_enumerator.rb +53 -0
  83. data/lib/ri_cal/property_value/recurrence_rule/numbered_span.rb +31 -0
  84. data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/by_day_incrementer.rb +86 -0
  85. data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/by_hour_incrementer.rb +31 -0
  86. data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/by_minute_incrementer.rb +32 -0
  87. data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/by_month_incrementer.rb +52 -0
  88. data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/by_monthday_incrementer.rb +31 -0
  89. data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/by_numbered_day_incrementer.rb +38 -0
  90. data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/by_second_incrementer.rb +32 -0
  91. data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/by_weekno_incrementer.rb +69 -0
  92. data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/by_yearday_incrementer.rb +31 -0
  93. data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/daily_incrementer.rb +28 -0
  94. data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/frequency_incrementer.rb +80 -0
  95. data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/hourly_incrementer.rb +23 -0
  96. data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/list_incrementer.rb +106 -0
  97. data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/minutely_incrementer.rb +23 -0
  98. data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/monthly_incrementer.rb +33 -0
  99. data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/null_sub_cycle_incrementer.rb +43 -0
  100. data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/secondly_incrementer.rb +28 -0
  101. data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/weekly_incrementer.rb +37 -0
  102. data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer/yearly_incrementer.rb +57 -0
  103. data/lib/ri_cal/property_value/recurrence_rule/occurrence_incrementer.rb +135 -0
  104. data/lib/ri_cal/property_value/recurrence_rule/recurring_day.rb +131 -0
  105. data/lib/ri_cal/property_value/recurrence_rule/recurring_month_day.rb +64 -0
  106. data/lib/ri_cal/property_value/recurrence_rule/recurring_numbered_week.rb +33 -0
  107. data/lib/ri_cal/property_value/recurrence_rule/recurring_year_day.rb +53 -0
  108. data/lib/ri_cal/property_value/recurrence_rule/time_manipulation.rb +42 -0
  109. data/lib/ri_cal/property_value/recurrence_rule/validations.rb +125 -0
  110. data/lib/ri_cal/property_value/recurrence_rule.rb +154 -0
  111. data/lib/ri_cal/property_value/text.rb +44 -0
  112. data/lib/ri_cal/property_value/uri.rb +11 -0
  113. data/lib/ri_cal/property_value/utc_offset.rb +33 -0
  114. data/lib/ri_cal/property_value/zulu_date_time.rb +34 -0
  115. data/lib/ri_cal/property_value.rb +159 -0
  116. data/lib/ri_cal/required_timezones.rb +55 -0
  117. data/lib/ri_cal.rb +187 -0
  118. data/parked_specs/ri_cal/claudio_a_bug_spec.rb +100 -0
  119. data/performance/empty_propval/subject.rb +43 -0
  120. data/performance/paris_eastern/subject.rb +90 -0
  121. data/performance/penultimate_weekday/subject.rb +15 -0
  122. data/performance/psm_big_enum/ical.ics +3171 -0
  123. data/performance/psm_big_enum/subject.rb +16 -0
  124. data/performance/utah_cycling/subject.rb +55 -0
  125. data/ri_cal.gemspec +244 -0
  126. data/script/benchmark_subject +23 -0
  127. data/script/console +10 -0
  128. data/script/destroy +14 -0
  129. data/script/generate +14 -0
  130. data/script/profile_subject +29 -0
  131. data/script/txt2html +71 -0
  132. data/spec/ri_cal/bugreports_spec.rb +276 -0
  133. data/spec/ri_cal/component/alarm_spec.rb +12 -0
  134. data/spec/ri_cal/component/calendar_spec.rb +88 -0
  135. data/spec/ri_cal/component/event_spec.rb +735 -0
  136. data/spec/ri_cal/component/freebusy_spec.rb +12 -0
  137. data/spec/ri_cal/component/journal_spec.rb +37 -0
  138. data/spec/ri_cal/component/t_z_info_timezone_spec.rb +60 -0
  139. data/spec/ri_cal/component/timezone_spec.rb +236 -0
  140. data/spec/ri_cal/component/todo_spec.rb +112 -0
  141. data/spec/ri_cal/component_spec.rb +224 -0
  142. data/spec/ri_cal/core_extensions/string/conversions_spec.rb +78 -0
  143. data/spec/ri_cal/core_extensions/time/calculations_spec.rb +188 -0
  144. data/spec/ri_cal/core_extensions/time/week_day_predicates_spec.rb +45 -0
  145. data/spec/ri_cal/fast_date_time_spec.rb +77 -0
  146. data/spec/ri_cal/inf_loop_spec.rb +78 -0
  147. data/spec/ri_cal/occurrence_enumerator_spec.rb +611 -0
  148. data/spec/ri_cal/parser_spec.rb +337 -0
  149. data/spec/ri_cal/property_value/date_spec.rb +53 -0
  150. data/spec/ri_cal/property_value/date_time_spec.rb +383 -0
  151. data/spec/ri_cal/property_value/duration_spec.rb +126 -0
  152. data/spec/ri_cal/property_value/occurrence_list_spec.rb +72 -0
  153. data/spec/ri_cal/property_value/period_spec.rb +63 -0
  154. data/spec/ri_cal/property_value/recurrence_rule/recurring_year_day_spec.rb +21 -0
  155. data/spec/ri_cal/property_value/recurrence_rule_spec.rb +1814 -0
  156. data/spec/ri_cal/property_value/text_spec.rb +25 -0
  157. data/spec/ri_cal/property_value/utc_offset_spec.rb +48 -0
  158. data/spec/ri_cal/property_value_spec.rb +125 -0
  159. data/spec/ri_cal/required_timezones_spec.rb +67 -0
  160. data/spec/ri_cal_spec.rb +53 -0
  161. data/spec/spec.opts +4 -0
  162. data/spec/spec_helper.rb +50 -0
  163. data/tasks/gem_loader/load_active_support.rb +3 -0
  164. data/tasks/gem_loader/load_tzinfo_gem.rb +2 -0
  165. data/tasks/ri_cal.rake +412 -0
  166. data/tasks/spec.rake +102 -0
  167. metadata +246 -0
@@ -0,0 +1,57 @@
1
+ module RiCal
2
+ class PropertyValue
3
+ class RecurrenceRule < PropertyValue
4
+ #- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
5
+ #
6
+ class OccurrenceIncrementer # :nodoc:
7
+ class YearlyIncrementer < FrequencyIncrementer #:nodoc:
8
+
9
+ attr_reader :wkst
10
+
11
+ def initialize(rrule, sub_cycle_incrementer)
12
+ @wkst = rrule.wkst_day
13
+ super(rrule, sub_cycle_incrementer)
14
+ end
15
+
16
+ def self.from_rrule(rrule, start_time)
17
+ conditional_incrementer(rrule, "YEARLY", ByMonthIncrementer)
18
+ end
19
+
20
+ def advance_what
21
+ :years
22
+ end
23
+
24
+ def step(date_time)
25
+ if contains_weeknum_incrementer?
26
+ result = date_time
27
+ multiplier.times do
28
+ result = result.at_start_of_next_iso_year(wkst)
29
+ end
30
+ result
31
+ else
32
+ super(date_time)
33
+ end
34
+ end
35
+
36
+ def start_of_cycle(date_time)
37
+ if contains_weeknum_incrementer?
38
+ date_time.at_start_of_iso_year(wkst)
39
+ elsif contains_daily_incrementer?
40
+ date_time.change(:month => 1, :day => 1)
41
+ else
42
+ date_time.change(:month => 1)
43
+ end
44
+ end
45
+
46
+ def end_of_occurrence(date_time)
47
+ if contains_weeknum_incrementer?
48
+ date_time.end_of_iso_year(wkst)
49
+ else
50
+ date_time.end_of_year
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,135 @@
1
+ module RiCal
2
+ class PropertyValue
3
+ class RecurrenceRule < PropertyValue
4
+ #- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
5
+ #
6
+ class OccurrenceIncrementer # :nodoc:
7
+
8
+ attr_accessor :sub_cycle_incrementer, :current_occurrence, :outer_range
9
+ attr_accessor :outer_incrementers
10
+ attr_accessor :contains_daily_incrementer, :contains_weeknum_incrementer
11
+ attr_reader :leaf_iterator
12
+ parent_path = "ri_cal/property_value/recurrence_rule/occurrence_incrementer"
13
+
14
+ autoload :ByDayIncrementer, "#{parent_path}/by_day_incrementer.rb"
15
+ autoload :ByHourIncrementer, "#{parent_path}/by_hour_incrementer.rb"
16
+ autoload :ByMinuteIncrementer, "#{parent_path}/by_minute_incrementer.rb"
17
+ autoload :ByMonthIncrementer, "#{parent_path}/by_month_incrementer.rb"
18
+ autoload :ByMonthdayIncrementer, "#{parent_path}/by_monthday_incrementer.rb"
19
+ autoload :ByNumberedDayIncrementer, "#{parent_path}/by_numbered_day_incrementer.rb"
20
+ autoload :BySecondIncrementer, "#{parent_path}/by_second_incrementer.rb"
21
+ autoload :ByYeardayIncrementer, "#{parent_path}/by_yearday_incrementer.rb"
22
+ autoload :ByWeekNoIncrementer, "#{parent_path}/by_weekno_incrementer.rb"
23
+ autoload :DailyIncrementer, "#{parent_path}/daily_incrementer.rb"
24
+ autoload :FrequencyIncrementer, "#{parent_path}/frequency_incrementer.rb"
25
+ autoload :HourlyIncrementer, "#{parent_path}/hourly_incrementer.rb"
26
+ autoload :ListIncrementer, "#{parent_path}/list_incrementer.rb"
27
+ autoload :MinutelyIncrementer, "#{parent_path}/minutely_incrementer.rb"
28
+ autoload :MonthlyIncrementer, "#{parent_path}/monthly_incrementer.rb"
29
+ autoload :NullSubCycleIncrementer, "#{parent_path}/null_sub_cycle_incrementer.rb"
30
+ autoload :SecondlyIncrementer, "#{parent_path}/secondly_incrementer.rb"
31
+ autoload :WeeklyIncrementer, "#{parent_path}/weekly_incrementer.rb"
32
+ autoload :YearlyIncrementer, "#{parent_path}/yearly_incrementer.rb"
33
+
34
+ include RecurrenceRule::TimeManipulation
35
+
36
+ def initialize(rrule, sub_cycle_incrementer)
37
+ self.sub_cycle_incrementer = sub_cycle_incrementer
38
+ @outermost = true
39
+ self.outer_incrementers = []
40
+ if sub_cycle_incrementer
41
+ self.contains_daily_incrementer = sub_cycle_incrementer.daily_incrementer? ||
42
+ sub_cycle_incrementer.contains_daily_incrementer?
43
+ self.contains_weeknum_incrementer = sub_cycle_incrementer.weeknum_incrementer?||
44
+ sub_cycle_incrementer.contains_weeknum_incrementer?
45
+ sub_cycle_incrementer.add_outer_incrementer(self)
46
+ else
47
+ self.sub_cycle_incrementer = NullSubCycleIncrementer
48
+ end
49
+ end
50
+
51
+ def self.from_rrule(recurrence_rule, start_time)
52
+ YearlyIncrementer.from_rrule(recurrence_rule, start_time)
53
+ end
54
+
55
+ def add_outer_incrementer(incrementer)
56
+ @outermost = false
57
+ self.outer_incrementers << incrementer
58
+ sub_cycle_incrementer.add_outer_incrementer(incrementer)
59
+ end
60
+
61
+ def outermost?
62
+ @outermost
63
+ end
64
+
65
+ def to_s
66
+ if sub_cycle_incrementer
67
+ "#{self.short_name}->#{sub_cycle_incrementer}"
68
+ else
69
+ self.short_name
70
+ end
71
+ end
72
+
73
+ def short_name
74
+ @short_name ||= self.class.name.split("::").last
75
+ end
76
+
77
+ # Return the next time after previous_occurrence generated by this incrementer
78
+ # But the occurrence is outside the current cycle of any outer incrementer(s) return
79
+ # nil which will cause the outer incrementer to step to its next cycle
80
+ def next_time(previous_occurrence)
81
+ @previous_occurrence = previous_occurrence
82
+ if current_occurrence
83
+ sub_occurrence = @sub_cycle_incrementer.next_time(previous_occurrence)
84
+ else #first time
85
+ sub_occurrence = @sub_cycle_incrementer.first_sub_occurrence(previous_occurrence, update_cycle_range(previous_occurrence))
86
+ end
87
+ if sub_occurrence
88
+ candidate = sub_occurrence
89
+ else
90
+ candidate = next_cycle(previous_occurrence)
91
+ end
92
+ if in_outer_cycle?(candidate)
93
+ candidate
94
+ else
95
+ nil
96
+ end
97
+ end
98
+
99
+ def update_cycle_range(date_time)
100
+ self.current_occurrence = date_time
101
+ (date_time..end_of_occurrence(date_time))
102
+ end
103
+
104
+ def in_outer_cycle?(candidate)
105
+ candidate && (outer_range.nil? || (outer_range.first <= candidate && outer_range.last >= candidate))
106
+ end
107
+
108
+ def first_sub_occurrence(previous_occurrence, outer_cycle_range)
109
+ first_within_outer_cycle(previous_occurrence, outer_cycle_range)
110
+ end
111
+
112
+ # Advance to the next cycle, if the result is within the current cycles of all outer incrementers
113
+ def next_cycle(previous_occurrence)
114
+ raise "next_cycle is a subclass responsibility"
115
+ end
116
+
117
+ def contains_daily_incrementer?
118
+ @contains_daily_incrementer
119
+ end
120
+
121
+ def daily_incrementer?
122
+ false
123
+ end
124
+
125
+ def contains_weeknum_incrementer?
126
+ @contains_weeknum_incrementer
127
+ end
128
+
129
+ def weeknum_incrementer?
130
+ false
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,131 @@
1
+ module RiCal
2
+ class PropertyValue
3
+ class RecurrenceRule < PropertyValue
4
+ #- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
5
+ #
6
+ # Instances of RecurringDay are used to represent values in BYDAY recurrence rule parts
7
+ #
8
+ class RecurringDay # :nodoc:
9
+
10
+ attr_reader :wday, :index, :rrule
11
+
12
+ DayNames = %w{SU MO TU WE TH FR SA} unless defined? DayNames
13
+ day_nums = {}
14
+ unless defined? DayNums
15
+ DayNames.each_with_index { |name, i| day_nums[name] = i }
16
+ DayNums = day_nums
17
+ end
18
+
19
+ attr_reader :source, :scope
20
+ def initialize(source, rrule, scope = :monthly)
21
+ @source = source
22
+ @rrule = rrule
23
+ @scope = scope
24
+ wd_match = source.match(/([+-]?\d*)(SU|MO|TU|WE|TH|FR|SA)/)
25
+ if wd_match
26
+ @day, @ordinal = wd_match[2], wd_match[1]
27
+ @wday = DayNums[@day]
28
+ @index = (@ordinal == "") ? nil : @ordinal.to_i
29
+ end
30
+ end
31
+
32
+ def valid?
33
+ !@day.nil?
34
+ end
35
+
36
+ def ==(another)
37
+ self.class === another && to_a = another.to_a
38
+ end
39
+
40
+ def to_a
41
+ [@day, @ordinal]
42
+ end
43
+
44
+ # return a list id for a given time to allow the enumerator to cache lists
45
+ def list_id(time)
46
+ case @scope
47
+ when :yearly
48
+ time.year
49
+ when :monthly
50
+ (time.year * 100) + time.month
51
+ when :weekly
52
+ time.at_start_of_week_with_wkst(rrule.wkst_day).jd
53
+ end
54
+ end
55
+
56
+ # return a list of times which match the time parameter within the scope of the RecurringDay
57
+ def matches_for(time)
58
+ case @scope
59
+ when :yearly
60
+ yearly_matches_for(time)
61
+ when :monthly
62
+ monthly_matches_for(time)
63
+ when :weekly
64
+ weekly_matches_for(time)
65
+ else
66
+ walkback = caller.grep(/recurrence/i)
67
+ raise "Logic error!#{@scope.inspect}\n #{walkback.join("\n ")}"
68
+ end
69
+ end
70
+
71
+ def yearly_matches_for(time)
72
+ if @ordinal == ""
73
+ t = time.nth_wday_in_year(1, wday)
74
+ result = []
75
+ year = time.year
76
+ while t.year == year
77
+ result << t
78
+ t = t.advance(:week => 1)
79
+ end
80
+ result
81
+ else
82
+ [time.nth_wday_in_year(@ordinal.to_i, wday)]
83
+ end
84
+ end
85
+
86
+ def monthly_matches_for(time)
87
+ if @ordinal == ""
88
+ t = time.nth_wday_in_month(1, wday)
89
+ result = []
90
+ month = time.month
91
+ while t.month == month
92
+ result << t
93
+ t = t.advance(:days => 7)
94
+ end
95
+ result
96
+ else
97
+ result = [time.nth_wday_in_month(index, wday)]
98
+ result
99
+ end
100
+ end
101
+
102
+ def weekly_matches_for(time)
103
+ date = time.start_of_week_with_wkst(rrule.wkst_day)
104
+ date += 1 while date.wday != wday
105
+ [time.change(:year => date.year, :month => date.month, :day => date.day)]
106
+ end
107
+
108
+ def to_s
109
+ "#{@ordinal}#{@day}"
110
+ end
111
+
112
+ def ordinal_match(date_or_time)
113
+ if @ordinal == "" || @scope == :weekly
114
+ true
115
+ else
116
+ if @scope == :yearly
117
+ date_or_time.nth_wday_in_year?(index, wday)
118
+ else
119
+ date_or_time.nth_wday_in_month?(index, wday)
120
+ end
121
+ end
122
+ end
123
+
124
+ # Determine if a particular date, time, or date_time is included in the recurrence
125
+ def include?(date_or_time)
126
+ date_or_time.wday == wday && ordinal_match(date_or_time)
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,64 @@
1
+ module RiCal
2
+ class PropertyValue
3
+ class RecurrenceRule < PropertyValue
4
+ #- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
5
+ #
6
+ # Instances of RecurringMonthDay represent BYMONTHDAY parts in recurrence rules
7
+ class RecurringMonthDay < NumberedSpan # :nodoc:
8
+
9
+ def last
10
+ 31
11
+ end
12
+
13
+ # return a list id for a given time to allow the enumerator to cache lists
14
+ def list_id(time)
15
+ time.month
16
+ end
17
+
18
+ # return a list of times which match the time parameter within the scope of the RecurringDay
19
+ def matches_for(time)
20
+ [time.change(:day => 1).advance(:days => target_for(time)- 1)]
21
+ end
22
+
23
+ # return a list id for a given time to allow the enumerator to cache lists
24
+ def list_id(time)
25
+ time.month
26
+ end
27
+
28
+ # return a list of times which match the time parameter within the scope of the RecurringDay
29
+ def matches_for(time)
30
+ [time.change(:day => 1).advance(:days => target_for(time)- 1)]
31
+ end
32
+
33
+ def target_date_time_for(date_time)
34
+ matches_for(date_time)[0]
35
+ end
36
+
37
+ # return a list of times which match the time parameter within the scope of the RecurringDay
38
+ def matches_for(time)
39
+ [time.change(:day => 1).advance(:days => target_for(time)- 1)]
40
+ end
41
+
42
+ def target_date_time_for(date_time)
43
+ matches_for(date_time)[0]
44
+ end
45
+
46
+ def fixed_day?
47
+ @source > 0
48
+ end
49
+
50
+ def target_for(date_or_time)
51
+ if fixed_day?
52
+ @source
53
+ else
54
+ date_or_time.days_in_month + @source + 1
55
+ end
56
+ end
57
+
58
+ def include?(date_or_time)
59
+ date_or_time.mday == target_for(date_or_time)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,33 @@
1
+ module RiCal
2
+ class PropertyValue
3
+ class RecurrenceRule < PropertyValue
4
+ #- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
5
+ #
6
+ class RecurringNumberedWeek < NumberedSpan # :nodoc:
7
+ def last
8
+ 53
9
+ end
10
+
11
+ def rule_wkst
12
+ @rule && rule.wkst_day
13
+ end
14
+
15
+ def default_wkst
16
+ rule_wkst || 1
17
+ end
18
+
19
+ def adjusted_iso_weeknum(date_or_time)
20
+ if @source > 0
21
+ @source
22
+ else
23
+ date_or_time.iso_weeks_in_year(wkst) + @source + 1
24
+ end
25
+ end
26
+
27
+ def include?(date_or_time, wkst=default_wkst)
28
+ date_or_time.iso_week_num(wkst) == adjusted_iso_weeknum(date_or_time)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,53 @@
1
+ module RiCal
2
+ class PropertyValue
3
+ class RecurrenceRule < PropertyValue
4
+ #- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
5
+ #
6
+ class RecurringYearDay < NumberedSpan # :nodoc:
7
+
8
+ def last
9
+ 366
10
+ end
11
+
12
+ def leap_year?(year)
13
+ year % 4 == 0 && (year % 400 == 0 || year % 100 != 0)
14
+ end
15
+
16
+
17
+ def length_of_year(year)
18
+ leap_year?(year) ? 366 : 365
19
+ end
20
+
21
+ # return a list id for a given time to allow the enumerator to cache lists
22
+ def list_id(time)
23
+ time.year
24
+ end
25
+
26
+ # return a list of times which match the time parameter within the scope of the RecurringDay
27
+ def matches_for(time)
28
+ [time.change(:month => 1, :day => 1).advance(:days => target_for(time)- 1)]
29
+ end
30
+
31
+ def target_date_time_for(date_time)
32
+ matches_for(date_time)[0]
33
+ end
34
+
35
+ def fixed_day?
36
+ @source > 0
37
+ end
38
+
39
+ def target_for(date_or_time)
40
+ if fixed_day?
41
+ @source
42
+ else
43
+ length_of_year(date_or_time.year) + @source + 1
44
+ end
45
+ end
46
+
47
+ def include?(date_or_time)
48
+ date_or_time.yday == target_for(date_or_time)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,42 @@
1
+ module RiCal
2
+ class PropertyValue
3
+ class RecurrenceRule < PropertyValue
4
+ #- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
5
+ #
6
+ module TimeManipulation #:nodoc:
7
+
8
+ def advance_day(date_time)
9
+ date_time.advance(:days => 1)
10
+ end
11
+
12
+ def first_hour_of_day(date_time)
13
+ date_time.change(:hour => 0)
14
+ end
15
+
16
+ def advance_week(date_time)
17
+ date_time.advance(:days => 7)
18
+ end
19
+
20
+ def first_day_of_week(wkst_day, date_time)
21
+ date_time.at_start_of_week_with_wkst(wkst_day)
22
+ end
23
+
24
+ def advance_month(date_time)
25
+ date_time.advance(:months => 1)
26
+ end
27
+
28
+ def first_day_of_month(date_time)
29
+ date_time.change(:day => 1)
30
+ end
31
+
32
+ def advance_year(date_time)
33
+ date_time.advance(:years => 1)
34
+ end
35
+
36
+ def first_day_of_year(date_time)
37
+ date_time.change(:month => 1, :day => 1)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,125 @@
1
+ module RiCal
2
+ class PropertyValue
3
+ class RecurrenceRule < PropertyValue
4
+ #- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
5
+ #
6
+ module Validations #:nodoc:
7
+ # Validate that the parameters of the reciever conform to RFC 2445
8
+ # If errors are found they will be added to the receivers errors
9
+ #
10
+ # Whenever any of the parameters are set, e.g. with:
11
+ # recurrence_rule.count = 2
12
+ # the errors will be reset
13
+ def valid?
14
+ validate if @errors.nil?
15
+ errors.empty?
16
+ end
17
+
18
+ # Return any errors found during validation
19
+ # See #valid?
20
+ def errors
21
+ @errors ||= []
22
+ end
23
+
24
+ def reset_errors # :nodoc:
25
+ @errors = nil
26
+ end
27
+
28
+ # Used by #valid? to validate that the parameters of the reciever conform to RFC 2445
29
+ # If errors are found they will be added to the receivers errors
30
+ #
31
+ # Whenever any of the parameters are set, e.g. with:
32
+ # recurrence_rule.count = 2
33
+ # the errors will be reset
34
+ def validate
35
+ @errors = []
36
+ validate_termination
37
+ validate_freq
38
+ validate_interval
39
+ validate_int_by_list(:bysecond, (0..59))
40
+ validate_int_by_list(:byminute, (0..59))
41
+ validate_int_by_list(:byhour, (0..23))
42
+ validate_int_by_list(:bymonth, (1..12))
43
+ validate_bysetpos
44
+ validate_byday_list
45
+ validate_bymonthday_list
46
+ validate_byyearday_list
47
+ validate_byweekno_list
48
+ validate_wkst
49
+ end
50
+
51
+ def validate_termination
52
+ errors << "COUNT and UNTIL cannot both be specified" if @count && @until
53
+ end
54
+
55
+ def validate_freq
56
+ if @freq
57
+ unless %w{
58
+ SECONDLY MINUTELY HOURLY DAILY
59
+ WEEKLY MONTHLY YEARLY
60
+ }.include?(@freq.upcase)
61
+ errors << "Invalid frequency '#{@freq}'"
62
+ end
63
+ else
64
+ errors << "RecurrenceRule must have a value for FREQ"
65
+ end
66
+ end
67
+
68
+ def validate_interval
69
+ if @interval
70
+ errors << "interval must be a positive integer" unless @interval > 0
71
+ end
72
+ end
73
+
74
+ def validate_wkst
75
+ errors << "#{wkst.inspect} is invalid for wkst" unless %w{MO TU WE TH FR SA SU}.include?(wkst)
76
+ end
77
+
78
+ def validate_int_by_list(which, test)
79
+ vals = by_list[which] || []
80
+ vals.each do |val|
81
+ errors << "#{val} is invalid for #{which}" unless test === val
82
+ end
83
+ end
84
+
85
+ def validate_bysetpos
86
+ vals = by_list[:bysetpos] || []
87
+ vals.each do |val|
88
+ errors << "#{val} is invalid for bysetpos" unless (-366..-1) === val || (1..366) === val
89
+ end
90
+ unless vals.empty?
91
+ errors << "bysetpos cannot be used without another by_xxx rule part" unless by_list.length > 1
92
+ end
93
+ end
94
+
95
+ def validate_byday_list
96
+ days = by_list[:byday] || []
97
+ days.each do |day|
98
+ errors << "#{day.source.inspect} is not a valid day" unless day.valid?
99
+ end
100
+ end
101
+
102
+ def validate_bymonthday_list
103
+ days = by_list[:bymonthday] || []
104
+ days.each do |day|
105
+ errors << "#{day.source.inspect} is not a valid month day" unless day.valid?
106
+ end
107
+ end
108
+
109
+ def validate_byyearday_list
110
+ days = by_list[:byyearday] || []
111
+ days.each do |day|
112
+ errors << "#{day.source.inspect} is not a valid year day" unless day.valid?
113
+ end
114
+ end
115
+
116
+ def validate_byweekno_list
117
+ days = by_list[:byweekno] || []
118
+ days.each do |day|
119
+ errors << "#{day.source.inspect} is not a valid week number" unless day.valid?
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end