texel-recurrence-rule-parser 0.0.4 → 0.0.5

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.
data/README.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  = RruleParser
2
2
 
3
- * FIX (url)
3
+ http://www.github.com/texel/recurrence-rule-parser
4
4
 
5
5
  == DESCRIPTION:
6
6
 
@@ -10,18 +10,35 @@ and handles translating recurrence rules into temporal expressions.
10
10
  == FEATURES/PROBLEMS:
11
11
 
12
12
  Current list of supported expressions:
13
- FREQ
14
- INTERVAL
15
- BYDAY
13
+ FREQ, INTERVAL, BYDAY, BYMONTHDAY, UNTIL
14
+
15
+ Many values are still unimplemented.
16
16
 
17
17
  == SYNOPSIS:
18
18
 
19
- code samples forthcoming.
19
+ RruleParser should be able to consume any object acting like Icalendar::Event, containing
20
+ recurrence rules as specified by the iCalendar (RFC 2445) specification. For more information
21
+ on formatting of recurrence rules, visit http://www.kanzaki.com/docs/ical/rrule.html
22
+
23
+ == EXAMPLES:
24
+
25
+ event = Icalendar::Event.new
26
+ event.start = Time.parse('12/1/2008 3pm')
27
+ event.end = Time.parse('12/1/2008 6pm')
28
+
29
+ event.recurrence_rules = ["FREQ=WEEKLY;INTERVAL=1;COUNT=10"]
30
+
31
+ parser = RruleParser.new(event)
32
+ date_range = Date.parse('11/30/2008')..Date.parse('1/1/2009')
33
+
34
+ parser.dates(date_range)
35
+
36
+ #=> [Mon, 01 Dec 2008, Mon, 08 Dec 2008, Mon, 15 Dec 2008, Mon, 22 Dec 2008, Mon, 29 Dec 2008]
20
37
 
21
38
  == REQUIREMENTS:
22
39
 
23
- requires Runt and Icalendar.
24
- requires RSpec if you wish to run the specs.
40
+ requires Runt.
41
+ requires Icalendar and RSpec if you wish to run the specs.
25
42
 
26
43
  == INSTALL:
27
44
 
data/lib/rrule_parser.rb CHANGED
@@ -1,5 +1,5 @@
1
+ require 'rubygems'
1
2
  require 'runt'
2
- require 'icalendar'
3
3
 
4
4
  class RruleParser
5
5
  VERSION = '1.0.0'
@@ -38,7 +38,7 @@ class RruleParser
38
38
  def expressions
39
39
  @expressions = []
40
40
  @expressions << parse_frequency_and_interval
41
- @expressions << parse_byday
41
+ @expressions << send(:"parse_#{self.rules[:freq].downcase}")
42
42
  @expressions << parse_start
43
43
  @expressions << parse_until
44
44
  @expressions.compact!
@@ -51,15 +51,22 @@ class RruleParser
51
51
 
52
52
  # Accepts a range of dates and outputs an array of dates matching the temporal expression.
53
53
  def dates(range)
54
+ dates = []
55
+
54
56
  if @count <= 0
55
- self.expression.dates(range)
57
+ dates << self.expression.dates(range)
56
58
  else
57
- temp_range = self.event.start.to_date..(range.last)
59
+ temp_range = (self.event.start.send :to_date)..(range.last)
58
60
  temp_dates = self.expression.dates(temp_range, @count)
59
- temp_dates.select do |date|
61
+ dates << temp_dates.select do |date|
60
62
  range.include?(date)
61
63
  end
62
64
  end
65
+
66
+ # TODO put original date back in if recurrence rule doesn't define it.
67
+ start_date = self.event.start.send(:to_date)
68
+ dates << start_date if range.include?(start_date)
69
+ dates.flatten.uniq
63
70
  end
64
71
 
65
72
  protected
@@ -83,7 +90,7 @@ class RruleParser
83
90
  end
84
91
 
85
92
  def parse_start
86
- start_date = Date.civil(self.event.start.year, self.event.start.month, self.event.start.day - 1)
93
+ start_date = Date.civil(self.event.start.year, self.event.start.month, self.event.start.day) - 1
87
94
  Runt::AfterTE.new(start_date)
88
95
  end
89
96
 
@@ -95,13 +102,53 @@ class RruleParser
95
102
  end
96
103
  end
97
104
 
98
- # Currently only supports days of the week (MO, TU, WED, etc.)
99
- def parse_byday
105
+ def parse_daily
106
+ return
107
+ end
108
+
109
+ def parse_weekly
110
+ if self.rules[:byday]
111
+ self.rules[:byday].map { |day| parse_byday(day) }.inject do |m, expr|
112
+ m | expr
113
+ end
114
+ else
115
+ # Make the event recur on the day of the original event.
116
+ Runt::DIWeek.new(self.event.start.wday)
117
+ end
118
+ end
119
+
120
+ def parse_monthly
100
121
  if self.rules[:byday]
101
- self.rules[:byday].map{ |day| Runt::DIWeek.new(RruleParser::DAYS[day]) }.inject do |m, expr|
122
+ self.rules[:byday].map do |day_string|
123
+ parse_byday(day_string)
124
+ end.inject {|m, expr| m | expr}
125
+ elsif self.rules[:bymonthday]
126
+ self.rules[:bymonthday].map { |day| Runt::REMonth.new(day.to_i) }.inject do |m, expr|
127
+ m | expr
128
+ end
129
+ else
130
+ Runt::REMonth.new(self.event.start.day, self.event.start.day)
131
+ end
132
+ end
133
+
134
+ def parse_yearly
135
+ expressions = []
136
+
137
+ if self.rules[:bymonth]
138
+ expressions << self.rules[:bymonth].map { |month| Runt::REYear.new(month.to_i) }.inject do |m, expr|
102
139
  m | expr
103
140
  end
141
+ else
142
+ expressions << Runt::REYear.new(self.event.start.month)
143
+ end
144
+
145
+ if self.rules[:byday]
146
+ expressions << self.rules[:byday].map { |day_string| parse_byday(day_string) }.inject {|m, v| m | v}
147
+ else
148
+ expressions << Runt::REMonth.new(self.event.start.day)
104
149
  end
150
+
151
+ expressions.inject {|m, expr| m & expr}
105
152
  end
106
153
 
107
154
  def parse_until
@@ -113,4 +160,15 @@ class RruleParser
113
160
  def parse_count
114
161
  @count = self.rules[:count].to_i if self.rules[:count]
115
162
  end
163
+
164
+ def parse_byday(day_string)
165
+ # BYDAY rules can be in one of two formats: 2TU (2nd Tuesday), or TU (every Tuesday)
166
+ if day_string =~ /\d/
167
+ day_index = day_string.to_i
168
+ day = DAYS[day_string.gsub(day_index.to_s, '')] # Why is abbreviation such a long word?
169
+ Runt::DIMonth.new(day_index, day)
170
+ else
171
+ Runt::DIWeek.new(RruleParser::DAYS[day_string])
172
+ end
173
+ end
116
174
  end
@@ -1,5 +1,7 @@
1
1
  require 'lib/rrule_parser'
2
2
  require 'icalendar'
3
+ require 'spec'
4
+ require 'redgreen'
3
5
 
4
6
  module RruleParserSpecHelper
5
7
  def create_default_event
@@ -12,6 +14,12 @@ module RruleParserSpecHelper
12
14
  @event.recurrence_rules = ["FREQ=#{@frequency};INTERVAL=#{@interval};BYDAY=#{@byday};WKST=SU"]
13
15
  end
14
16
 
17
+ def create_event
18
+ @event = Icalendar::Event.new
19
+ end
20
+
21
+ # TODO Make other test objects for shared specs :)
22
+
15
23
  def create_parser(event)
16
24
  @parser = RruleParser.new(event)
17
25
  end
@@ -76,7 +84,7 @@ describe RruleParser do
76
84
  @result = @parser.send(:parse_frequency_and_interval)
77
85
  end
78
86
 
79
- it "return a valid temporal expression" do
87
+ it "returns a valid temporal expression" do
80
88
  @result.should be_an_instance_of(Runt::EveryTE)
81
89
  end
82
90
 
@@ -88,4 +96,210 @@ describe RruleParser do
88
96
  @result.instance_variable_get("@precision").should == Runt::DPrecision.const_get(RruleParser::ADVERB_MAP[@frequency])
89
97
  end
90
98
  end
99
+
100
+ describe "#dates" do
101
+ context "with an event starting on Monday, 12/1/2008" do
102
+ before(:each) do
103
+ create_event
104
+ @event.start = Time.parse('12/1/2008 3pm')
105
+ @event.end = Time.parse('12/1/2008 5pm')
106
+ end
107
+
108
+ context "with a one-month range" do
109
+ before(:each) do
110
+ @range = (Date.civil(2008, 12, 1)..(Date.civil(2009, 1, 1)))
111
+ end
112
+
113
+ context "recurring every week" do
114
+ before(:each) do
115
+ @event.recurrence_rules = ['FREQ=WEEKLY;INTERVAL=1']
116
+ @range = (Date.civil(2008, 12, 1)..(Date.civil(2009, 1, 1)))
117
+ create_parser(@event)
118
+ end
119
+
120
+ it "should return 5 dates" do
121
+ @parser.dates(@range).size.should == 5
122
+ end
123
+
124
+ it "should return only Monday dates" do
125
+ @parser.dates(@range).map {|d| d.wday == Runt::Monday }.all?.should be_true
126
+ end
127
+ end
128
+
129
+ context "recurring every other week" do
130
+ before(:each) do
131
+ @event.recurrence_rules = ['FREQ=WEEKLY;INTERVAL=2']
132
+ @range = (Date.civil(2008, 12, 1)..(Date.civil(2008, 12, 31)))
133
+ create_parser(@event)
134
+ end
135
+
136
+ it "should return 3 dates" do
137
+ @parser.dates(@range).size.should == 3
138
+ end
139
+ end
140
+
141
+ context "recurring every Monday, Wednesday, and Friday" do
142
+ before(:each) do
143
+ @event.recurrence_rules = ['FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,WE,FR']
144
+ create_parser(@event)
145
+ end
146
+
147
+ it "should return 14 dates" do
148
+ @parser.dates(@range).size.should == 14
149
+ end
150
+
151
+ it "should return only dates on Monday, Wednesday, and Friday" do
152
+ @parser.dates(@range).map {|d| [Runt::Mon, Runt::Wed, Runt::Fri].include?(d.wday) }.all?.should be_true
153
+ end
154
+ end
155
+
156
+ context "recurring every day" do
157
+ before(:each) do
158
+ @event.recurrence_rules = ['FREQ=DAILY;INTERVAL=1']
159
+ create_parser(@event)
160
+ end
161
+
162
+ it "should return 31 dates" do
163
+ @parser.dates(@range).size.should == 32
164
+ end
165
+ end
166
+
167
+ context "recurring every 2 days" do
168
+ before(:each) do
169
+ @event.recurrence_rules = ['FREQ=DAILY;INTERVAL=2']
170
+ create_parser(@event)
171
+ end
172
+
173
+ it "should return 31 dates" do
174
+ @parser.dates(@range).size.should == 16
175
+ end
176
+ end
177
+ end
178
+
179
+ context "with a one-year range" do
180
+ before(:each) do
181
+ @range = (Date.civil(2008, 12, 1)..(Date.civil(2009, 11, 30)))
182
+ end
183
+
184
+ context "recurring every month" do
185
+ before(:each) do
186
+ @event.recurrence_rules = ['FREQ=MONTHLY;INTERVAL=1']
187
+ create_parser(@event)
188
+ end
189
+
190
+ it "should return 12 dates" do
191
+ @parser.dates(@range).size.should == 12
192
+ end
193
+
194
+ it "should return only dates on the same day of the month" do
195
+ @parser.dates(@range).map {|d| d.day == @event.start.day}.all?.should be_true
196
+ end
197
+ end
198
+
199
+ context "recurring every two months" do
200
+ before(:each) do
201
+ @event.recurrence_rules = ['FREQ=MONTHLY;INTERVAL=2']
202
+ create_parser(@event)
203
+ end
204
+
205
+ it "should return 6 dates" do
206
+ @parser.dates(@range).size.should == 6
207
+ end
208
+
209
+ it "should return only dates on the same day of the month" do
210
+ @parser.dates(@range).map {|d| d.day == @event.start.day}.all?.should be_true
211
+ end
212
+ end
213
+
214
+ context "recurring the first and fifteenth of every month" do
215
+ before(:each) do
216
+ @event.recurrence_rules = ['FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=1,15']
217
+ create_parser(@event)
218
+ end
219
+
220
+ it "should return 24 dates" do
221
+ @parser.dates(@range).size.should == 24
222
+ end
223
+
224
+ it "should return dates only on the 1st and 15th of the month" do
225
+ @parser.dates(@range).map {|d| [1, 15].include?(d.day) }.all?.should be_true
226
+ end
227
+ end
228
+
229
+ context "recurring the first Monday of every month" do
230
+ before(:each) do
231
+ @event.recurrence_rules = ['FREQ=MONTHLY;INTERVAL=1;BYDAY=1MO']
232
+ create_parser(@event)
233
+ end
234
+
235
+ it "should return 12 dates" do
236
+ @parser.dates(@range).size.should == 12
237
+ end
238
+
239
+ it "should return only Mondays" do
240
+ # Garfield hates this recurrence rule.
241
+ @parser.dates(@range).map {|d| d.wday == Runt::Monday}.all?.should be_true
242
+ end
243
+ end
244
+ end
245
+
246
+ context "with a five-year range" do
247
+ before(:each) do
248
+ @range = (Date.civil(2008, 12, 1)..(Date.civil(2013, 11, 30)))
249
+ end
250
+
251
+ context "recurring every year" do
252
+ before do
253
+ @event.recurrence_rules = ['FREQ=YEARLY;INTERVAL=1']
254
+ create_parser(@event)
255
+ @dates = @parser.dates(@range)
256
+ end
257
+
258
+ it "should return 5 dates" do
259
+ @dates.size.should == 5
260
+ end
261
+
262
+ it "should return the correct date each year" do
263
+ @dates.map do |d|
264
+ [:month, :day].map do |method|
265
+ d.send(method) == @event.start.send(method)
266
+ end.all?
267
+ end.all?.should be_true
268
+ end
269
+ end
270
+
271
+ context "recurring every other year" do
272
+ before do
273
+ @event.recurrence_rules = ['FREQ=YEARLY;INTERVAL=2']
274
+ create_parser(@event)
275
+ @dates = @parser.dates(@range)
276
+ end
277
+
278
+ it "should return 3 dates" do
279
+ @dates.size.should == 3
280
+ end
281
+
282
+ it "should return the correct date each year" do
283
+ @dates.map do |d|
284
+ [:month, :day].map do |method|
285
+ d.send(method) == @event.start.send(method)
286
+ end.all?
287
+ end.all?.should be_true
288
+ end
289
+ end
290
+
291
+ context "recurring every December and June" do
292
+ before do
293
+ @event.recurrence_rules = ['FREQ=YEARLY;INTERVAL=1;BYMONTH=6,12']
294
+ create_parser(@event)
295
+ @dates = @parser.dates(@range)
296
+ end
297
+
298
+ it "should return 10 dates" do
299
+ @dates.size.should == 10
300
+ end
301
+ end
302
+ end
303
+ end
304
+ end
91
305
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: texel-recurrence-rule-parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leigh Caplan