texel-recurrence-rule-parser 0.0.4 → 0.0.5

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