recurring 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,5 @@
1
+ == 0.1.0 / 2006-12-09
2
+
3
+ * Initial releast
4
+ * Handles the basic API: include? find_in_range
5
+
data/Manifest.txt ADDED
@@ -0,0 +1,6 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ lib/recurring.rb
6
+ spec/recurring_spec.rb
data/README.txt ADDED
@@ -0,0 +1,65 @@
1
+ Recurring::Schedule
2
+ by Chris Anderson
3
+ http://recurring.rubyforge.org
4
+
5
+ == DESCRIPTION:
6
+
7
+ Recurring allows you to define Schedules, which can tell you whether or not a given Time falls in the Schedule, as well as being able to return a list of times which match the Schedule within a given range.
8
+
9
+ Schedules can be like:
10
+ * Every Tuesday at 5pm
11
+ * The 1st and 15th of every other month
12
+ * Every 2 hours
13
+ * The first Friday of every month at 11:15am and 4:45:15pm
14
+
15
+ == FEATURES/PROBLEMS:
16
+
17
+ = Features
18
+ * Fast searching of long ranges for matching times.
19
+ * Ability to define Schedules in a few flexible ways.
20
+ * Extensive RSpec specifications (run "rake spec" to see them)
21
+ * Extensive RSpec specifications (run "rake spec" to see them)
22
+
23
+ = Problems / Todo
24
+ * Untested timezone support
25
+ * Plans to offer complex Schedule and Masks
26
+
27
+ == SYNOPSYS:
28
+
29
+ # This Schedule will match every Tuesday at 5pm
30
+ @rs = Recurring::Schedule.new :unit => 'weeks', :weekdays => 'tuesday', :times => '5pm'
31
+ @rs.include?(Time.utc(2006,12,12,17)) #=> true
32
+ @rs.include?(Time.utc(2006,12,13,17)) #=> false
33
+
34
+ == REQUIREMENTS:
35
+
36
+ * RSpec >= 0.7.4 to run the specs.
37
+
38
+ == INSTALL:
39
+
40
+ * just run <tt>gem install recurring</tt>
41
+
42
+ == LICENSE:
43
+
44
+ (The MIT License)
45
+
46
+ Copyright (c) 2006 Chris Anderson
47
+
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of this software and associated documentation files (the
50
+ 'Software'), to deal in the Software without restriction, including
51
+ without limitation the rights to use, copy, modify, merge, publish,
52
+ distribute, sublicense, and/or sell copies of the Software, and to
53
+ permit persons to whom the Software is furnished to do so, subject to
54
+ the following conditions:
55
+
56
+ The above copyright notice and this permission notice shall be
57
+ included in all copies or substantial portions of the Software.
58
+
59
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
60
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
61
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
62
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
63
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
64
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
65
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,25 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ #require_gem 'rspec'
6
+ #require 'rspec/lib/spec/rake/spectask'
7
+ require './lib/recurring.rb'
8
+
9
+ Hoe.new('recurring', Recurring::VERSION) do |p|
10
+ p.rubyforge_name = 'recurring'
11
+ p.summary = 'A scheduling library for recurring events'
12
+ p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
13
+ p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
14
+ p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
15
+ end
16
+
17
+ desc "Run the specs"
18
+ task :spec do
19
+ system "spec spec/recurring_spec.rb -f specdoc"
20
+ end
21
+ #Spec::Rake::SpecTask.new('spec') do |t|
22
+ # t.spec_files = FileList['examples/**/*.rb']
23
+ #end
24
+
25
+ # vim: syntax=Ruby
data/lib/recurring.rb ADDED
@@ -0,0 +1,304 @@
1
+ module Recurring
2
+ VERSION = '0.1.0'
3
+
4
+ class << self
5
+ # returns a number starting with 1
6
+ def week_in_month date
7
+ (((date.day - 1).to_f / 7.0) + 1).floor
8
+ end
9
+
10
+ # just a wrapper for strftime
11
+ def week_of_year date
12
+ date.strftime('%U').to_i
13
+ end
14
+ end
15
+
16
+
17
+ # Initialize a Schedule object with the proper options to calculate occurances in
18
+ # that schedule. Schedule#new take a hash of options <tt>:unit, :frequency, :anchor, :weeks, :monthdays, :weekdays, :times</tt>
19
+ #
20
+ # = Monthly
21
+ # [Every two months from an anchor time] <tt>Recurring::Schedule.new :unit => 'months', :frequency => 2, :anchor => Time.utc(2006,4,15,10,30)</tt>
22
+ # [The first and fifteenth of every month] <tt>Recurring::Schedule.new :unit => 'months', :monthdays => [1,15]</tt>
23
+ # [The first and eighteenth of every third month] <tt>Recurring::Schedule.new :unit => 'months', :frequency => 3, :anchor => Time.utc(2006,4,15,10,30), :monthdays => [10,18]</tt>
24
+ # [The third Monday of every month at 6:30pm] <tt>Recurring::Schedule.new :unit => 'months', :weeks => 3, :weekdays => :monday, :times => '6:30pm'</tt>
25
+ # = Weekly
26
+ # [Monday, Wednesday, and Friday of every week] <tt>Recurring::Schedule.new :unit => 'weeks', :weekdays => %w{monday weds friday}</tt>
27
+ # [Every week at the same time on the same day of the week as the anchor (Weds at 5:30pm)] <tt>Recurring::Schedule.new :unit => 'weeks', :anchor => Time.utc(2006,12,6,17,30)</tt>
28
+ # [Equivalently, Every Wednesday at 5:30] <tt>Recurring::Schedule.new :unit => 'weeks', :weekdays => 'weds', :times => '5:30pm'</tt>
29
+ # = Daily
30
+ # [Everyday at the time of the anchor] <tt>Recurring::Schedule.new :unit => 'days', :anchor => Time.utc(2006,11,1,10,15,22)</tt>
31
+ # [Everyday at 7am and 5:45:20pm] <tt>Recurring::Schedule.new :unit => 'days', :times => '7am 5:45:20pm'</tt>
32
+ # = Hourly
33
+ # [Every hour at 15 minutes, 30 minutes, and 45 minutes and 30 seconds] <tt>Recurring::Schedule.new :unit => 'hours', :times => '0:15 4:30 0:45:30'</tt>
34
+ # [Offset every 2 hours from the anchor] <tt>Recurring::Schedule.new :unit => 'hours', :anchor => Time.utc(2001,5,15,11,17)</tt>
35
+ # = Minutely
36
+ # [Every five minutes offset from the anchor] <tt>Recurring::Schedule.new :unit => 'minutes', :frequency => 5, :anchor => Time.utc(2006,9,1,10,30)</tt>
37
+ # [Every minute at second 15] <tt>Recurring::Schedule.new :unit => 'minutes', :times => '0:0:15'</tt>
38
+ #
39
+ # See the specs using "rake spec" for even more examples.
40
+ class Schedule
41
+
42
+ attr_reader :unit, :frequency, :anchor, :weeks, :monthdays, :weekdays, :times
43
+
44
+ # * options is a hash with keys <tt>:unit, :frequency, :anchor, :weeks, :monthdays, :weekdays, :times</tt>
45
+ # * valid units are <tt>months, weeks, days, hours, and minutes</tt>
46
+ # * :frequency defaults to 1
47
+ # * :anchor is required if the frequency is other than one
48
+ # * :weeks alongside :weekdays is used to specify the nth instance of a weekday in a month.
49
+ # * :weekdays takes an array of strings like <tt>%w{monday weds friday}</tt>
50
+ # * :monthdays takes an array of days of the month, eg. <tt>[1,7,15]</tt>
51
+ # * :times takes a string with a simple format. <tt>"4pm 5:15pm 6:45:30pm"</tt>
52
+ def initialize options
53
+ raise ArgumentError, 'specify a valid unit' unless options[:unit] &&
54
+ %w{months weeks days hours minutes}.include?(options[:unit])
55
+ raise ArgumentError, 'frequency > 1 requires an anchor Time' if options[:frequency] && !options[:anchor]
56
+ @unit = options[:unit].to_sym
57
+ @frequency = options[:frequency] || 1
58
+ @anchor = options[:anchor]
59
+ @times = parse_times options[:times]
60
+ @weeks = Array(options[:weeks]) if options[:weeks]
61
+ @weekdays = Array(options[:weekdays]).collect{|d|d.to_sym} if options[:weekdays]
62
+ @monthdays = Array(options[:monthdays]) if options[:monthdays]
63
+
64
+ @anchor_multiple = options[:times].nil? && options[:weeks].nil? && options[:weekdays].nil? && options[:monthdays].nil?
65
+ end
66
+
67
+
68
+ # Returns true or false depending on whether or not the time is included in the schedule.
69
+ def include? date
70
+ @resolution = nil
71
+ return true if check_anchor? && date == @anchor
72
+ return mismatch(:month) unless month_matches?(date) if @unit == :months
73
+ return mismatch(:week) unless week_matches?(date) if [:months, :weeks].include?(@unit)
74
+ if [:months, :weeks, :days].include?(@unit)
75
+ return mismatch(:day) unless day_matches?(date)
76
+ return mismatch(:time) unless time_matches?(date)
77
+ end
78
+ if @unit == :hours
79
+ return mismatch(:hour) unless hour_matches?(date)
80
+ return mismatch(:sub_hour) unless sub_hour_matches?(date)
81
+ end
82
+ if @unit == :minutes
83
+ return mismatch(:minute) unless minute_matches?(date)
84
+ return mismatch(:second) unless second_matches?(date)
85
+ end
86
+ @resolution = nil
87
+ true
88
+ end
89
+
90
+ # Starts from the argument, and returns the next included time. Returns the argument if it is included in the schedule.
91
+ def find_next date
92
+ loop do
93
+ return date if include?(date)
94
+ date = beginning_of_next @resolution, date
95
+ end
96
+ end
97
+
98
+ # Takes a range, which can specified as two Times, or as an object that returns times from the methods .first and .last
99
+ #
100
+ # <tt>rs.find_in_range(Time.now, Time.now+24*60*60)</tt>
101
+ #
102
+ # or
103
+ #
104
+ # <tt>range = (Time.now..Time.now+24*60*60)</tt>
105
+ #
106
+ # <tt>rs.find_in_range(range)</tt>
107
+ def find_in_range *range
108
+ range = range[0] if range.length == 1
109
+ start = range.first
110
+ result = []
111
+ loop do
112
+ rnext = find_next start
113
+ break if rnext > range.last
114
+ result << rnext
115
+ start = rnext + 1
116
+ end
117
+ result
118
+ end
119
+
120
+ # Two Schedules are equal if they have the same attributes.
121
+ def == other
122
+ [:unit, :frequency, :anchor, :weeks, :monthdays, :weekdays, :times].all? do |attribute|
123
+ self.send(attribute) == other.send(attribute)
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ def beginning_of_next scope, date
130
+ case scope
131
+ when :year
132
+ Time.utc(date.year + 1)
133
+ when :month
134
+ date.month < 12 ? Time.utc(date.year, date.month+1) : beginning_of_next(:year, date)
135
+ when :week
136
+ to_sunday = 7 - date.wday
137
+ next_week = (date + to_sunday*24*60*60)
138
+ Time.utc(next_week.year, next_week.month, next_week.day)
139
+ when :day
140
+ dayp = date + (24*60*60)
141
+ Time.utc(dayp.year, dayp.month, dayp.day)
142
+ when :time
143
+ next_time date
144
+ else
145
+ date + 1
146
+ end
147
+ end
148
+
149
+ def next_time date
150
+ me = {:hour => date.hour, :minute => date.min, :second => date.sec, :me => true}
151
+ my_times = times + [me]
152
+ my_times += [{:hour => @anchor.hour, :minute => @anchor.min, :second => @anchor.sec}] if check_anchor?
153
+ my_times.sort! do |a,b|
154
+ v = a[:hour] <=> b[:hour]
155
+ v = a[:minute] <=> b[:minute] if v == 0
156
+ v = a[:second] <=> b[:second] if v == 0
157
+ v
158
+ end
159
+ ntime = my_times[my_times.index(me)+1]
160
+ if ntime
161
+ Time.utc(date.year, date.month, date.day, ntime[:hour], ntime[:minute], ntime[:second])
162
+ else
163
+ beginning_of_next :day, date
164
+ end
165
+ end
166
+
167
+ def mismatch unit
168
+ @resolution = unit
169
+ false
170
+ end
171
+
172
+ def month_matches? date
173
+ #only concerned with multiples
174
+ return true if @frequency == 1
175
+ (date.month - @anchor.month) % @frequency == 0
176
+ end
177
+
178
+ def week_matches? date
179
+ if @unit == :weeks
180
+ return true if @frequency == 1
181
+ return (Recurring.week_of_year(date) - Recurring.week_of_year(@anchor)) % @frequency == 0
182
+ end
183
+ if @weeks
184
+ @weeks.include?(Recurring.week_in_month(date))
185
+ else
186
+ true
187
+ end
188
+ end
189
+
190
+ def day_matches? date
191
+ if @unit == :days
192
+ return true if @frequency == 1
193
+ return (date.day - @anchor.day) % @frequency == 0
194
+ end
195
+ return @monthdays.include?(date.day) if @monthdays
196
+ if @weekdays
197
+ day_nums = @weekdays.collect{|w| ordinal_weekday w}
198
+ return day_nums.include?(date.wday)
199
+ end
200
+ if @unit == :weeks && check_anchor?
201
+ return @anchor.wday == date.wday
202
+ end
203
+ return true if check_anchor? && date.day == @anchor.day
204
+ end
205
+
206
+ def time_matches? date
207
+ #concerned with groups of hour minute second
208
+ if check_anchor?
209
+ return @anchor.hour == date.hour && @anchor.min == date.min && @anchor.sec == date.sec
210
+ end
211
+ @times.any? do |time|
212
+ time[:hour] == date.hour && time[:minute] == date.min && time[:second] == date.sec
213
+ end
214
+ end
215
+
216
+ def hour_matches? date
217
+ #only concerned with multiples
218
+ return true if @frequency == 1
219
+ (date.hour - @anchor.hour) % @frequency == 0
220
+ end
221
+
222
+ def sub_hour_matches? date
223
+ if check_anchor?
224
+ return @anchor.min == date.min && @anchor.sec == date.sec
225
+ end
226
+ times.any? do |time|
227
+ time[:minute] == date.min && time[:second] == date.sec
228
+ end
229
+ end
230
+
231
+ def minute_matches? date
232
+ #only concerned with multiples
233
+ return true if @frequency == 1
234
+ (date.min - @anchor.min) % @frequency == 0
235
+ end
236
+
237
+ def second_matches? date
238
+ if check_anchor?
239
+ return @anchor.sec == date.sec
240
+ end
241
+ times.any? do |time|
242
+ time[:second] == date.sec
243
+ end
244
+ end
245
+
246
+ def check_anchor?
247
+ @anchor && @anchor_multiple
248
+ end
249
+
250
+ def ordinal_weekday symbol
251
+ lookup = {0 => [:sunday, :sun],
252
+ 1 => [:monday, :mon],
253
+ 2 => [:tuesday, :tues],
254
+ 3 => [:wednesday, :weds],
255
+ 4 => [:thursday, :thurs],
256
+ 5 => [:friday, :fri],
257
+ 6 => [:saturday, :sat]}
258
+ lookup.select{|k,v| v.include?(symbol)}.first.first
259
+ end
260
+
261
+ # def ordinal_month symbol
262
+ # lookup = {1 => [:january, :jan],
263
+ # 2 => [:february, :feb],
264
+ # 3 => [:march, :mar],
265
+ # 4 => [:april, :apr],
266
+ # 5 => [:may],
267
+ # 6 => [:june, :jun],
268
+ # 7 => [:july, :jul],
269
+ # 8 => [:august, :aug],
270
+ # 9 => [:september, :sept],
271
+ # 10 => [:october, :oct],
272
+ # 11 => [:november, :nov],
273
+ # 12 => [:december, :dec]}
274
+ # lookup.select{|k,v| v.include?(symbol)}.first.first
275
+ # end
276
+
277
+ def parse_times string
278
+ unless string
279
+ return [{:hour => 0, :minute => 0, :second => 0}]
280
+ end
281
+ times = string.downcase.gsub(',','').split(' ')
282
+ parsed = times.collect do |st|
283
+ st = st.gsub /pm|am/, ''
284
+ am_pm = $&
285
+ time = {}
286
+ time[:hour], time[:minute], time[:second] = st.split(':').collect {|n| n.to_i}
287
+ time[:minute] ||= 0
288
+ time[:second] ||= 0
289
+ time[:hour] = time[:hour] + 12 if am_pm == 'pm'
290
+ time
291
+ end
292
+ parsed
293
+ end
294
+ end
295
+ end
296
+
297
+
298
+ # RDS does not match ranges as such, just times specified to varying precision
299
+ # eg, you can construct a Schedule that matches all of February, by not specifying the
300
+ # week, day, or time. If you want February through August, you'll have to specify all the months
301
+ # individually.
302
+
303
+ # Change of Behaviour in Recurring: Schedules include only points in time. The Mask model handles ranges.
304
+
@@ -0,0 +1,602 @@
1
+ require 'rubygems'
2
+ require_gem 'rspec'
3
+ require File.dirname(__FILE__) + "/../lib/recurring"
4
+ require 'yaml'
5
+
6
+ def results_should_be_included results, schedule
7
+ results.each do |t|
8
+ schedule.should_include t
9
+ end
10
+ end
11
+
12
+ def should_find_in_range schedule, length, *range
13
+ result = schedule.find_in_range(range.first, range.last)
14
+ result.length.should_be length
15
+ results_should_be_included result, schedule
16
+ end
17
+
18
+ context "Initializing a Schedule" do
19
+
20
+ specify "should accept the frequency, unit, and anchor params" do
21
+ rs = Recurring::Schedule.new :unit => 'days', :frequency => 2, :anchor => Time.utc(2006,8,11)
22
+ rs.unit.should_be :days
23
+ rs.frequency.should_be 2
24
+ rs.anchor.should == Time.utc(2006,8,11)
25
+ end
26
+
27
+ specify "should check for valid units" do
28
+ lambda{@rs = Recurring::Schedule.new :unit => 'day', :times => '4:29am'}.should_raise ArgumentError
29
+ end
30
+
31
+ specify "should parse the time param" do
32
+ rs = Recurring::Schedule.new :unit => 'days', :times => '4:30pm 5pm 3:30:30'
33
+ rs.times.should == [{:hour => 16, :minute => 30, :second => 0}, {:hour => 17, :minute => 0, :second => 0}, {:hour => 3, :minute => 30, :second => 30}]
34
+ end
35
+
36
+ specify "should provide a sensible default time param" do
37
+ rs = Recurring::Schedule.new :unit => 'days'
38
+ rs.times.should == [{:hour => 0, :minute => 0, :second => 0}]
39
+ end
40
+
41
+ specify "should flip out if the units are not provided" do
42
+ lambda{Recurring::Schedule.new :argument => 'no units, dogg'}.should_raise ArgumentError
43
+ end
44
+
45
+ specify "should accept weeks and days params" do
46
+ rs = Recurring::Schedule.new :unit => 'months', :weeks => [1,2], :weekdays => %w{monday wednesday}
47
+ rs.weeks.should == [1,2]
48
+ rs.weekdays.should == [:monday, :wednesday]
49
+ end
50
+
51
+ specify "should accept days as integers" do
52
+ rs = Recurring::Schedule.new :unit => 'months', :monthdays => [1,15]
53
+ rs.monthdays.should == [1,15]
54
+ end
55
+
56
+ end
57
+
58
+ context "A complex Schedule" do
59
+ setup do
60
+ @rs = Recurring::Schedule.new :unit => 'months', :frequency => 2, :anchor => Time.utc(2006,4,15,10,30), :monthdays => [3,7], :times => '5pm 4:45:12am'
61
+ end
62
+
63
+ specify "should be marshallable" do
64
+ rm = Marshal.dump(@rs)
65
+ Marshal.load(rm).should == @rs
66
+ end
67
+
68
+ specify "should be YAMLable" do
69
+ rm = YAML.dump(@rs)
70
+ YAML.load(rm).should == @rs
71
+ end
72
+
73
+ specify "should be equal to a schedule with the same params" do
74
+ r2 = Recurring::Schedule.new :unit => 'months', :frequency => 2, :anchor => Time.utc(2006,4,15,10,30), :monthdays => [3,7], :times => '5pm 4:45:12am'
75
+ r2.should == @rs
76
+ end
77
+
78
+ specify "should not be equal to a slightly different schedule" do
79
+ r2 = Recurring::Schedule.new :unit => 'months', :frequency => 2, :anchor => Time.utc(2006,4,15,10,30), :monthdays => [3,7], :times => '5pm 4:45:11am'
80
+ r2.should_not_equal @rs
81
+ end
82
+
83
+ end
84
+
85
+ #MONTHS
86
+
87
+ context "A bi-monthly Schedule without more params" do
88
+
89
+ setup do
90
+ @rs = Recurring::Schedule.new :unit => 'months', :frequency => 2, :anchor => Time.utc(2006,4,15,10,30)
91
+ end
92
+
93
+ specify "should include the anchor date" do
94
+ @rs.should_include Time.utc(2006,4,15,10,30)
95
+ end
96
+
97
+ specify "should include dates offset by the frequency" do
98
+ @rs.should_include Time.utc(2006,6,15,10,30)
99
+ end
100
+
101
+ specify "should not include the beginnings of days for which it includes points in the middle" do
102
+ @rs.should_not_include Time.utc(2006,6,15)
103
+ @rs.should_not_include Time.utc(2006,4,15)
104
+ end
105
+
106
+ specify "should not include dates with wrong months" do
107
+ @rs.should_not_include Time.utc(2006,5,15,10,30)
108
+ end
109
+
110
+ specify "should not include dates without matching time parts" do
111
+ @rs.should_not_include Time.utc(2006,4,16)
112
+ @rs.should_not_include Time.utc(2006,6,15,10,15)
113
+ end
114
+
115
+ specify "should find that the start point is the next included date when the start point matches" do
116
+ included = Time.utc(2006,6,15,10,30)
117
+ @rs.find_next(included).should == included
118
+ end
119
+
120
+ specify "should find the next date when the start point doesn't match" do
121
+ @rs.find_next(Time.utc(2006,6,15)).should == Time.utc(2006,6,15,10,30)
122
+ end
123
+
124
+ end
125
+
126
+ context "A monthly schedule with monthday params" do
127
+ setup do
128
+ @rs = Recurring::Schedule.new :unit => 'months', :monthdays => [1,15]
129
+ # @rs.anchor = Time.utc(2006,6,18,12,30) #should react appropriately to the presence of a (no longer useless) anchor
130
+ end
131
+
132
+ specify "should include the start of matching days" do
133
+ @rs.should_include Time.utc(2006,6,15)
134
+ @rs.should_include Time.utc(2006,7,1)
135
+ end
136
+
137
+ specify "should not include the middle of matching days" do
138
+ @rs.should_not_include Time.utc(2006,6,15,14)
139
+ @rs.should_not_include Time.utc(2006,7,1,11,30)
140
+ @rs.should_not_include Time.utc(2006,7,1,0,0,30)
141
+ end
142
+
143
+ specify "should not include the start of non-matching days" do
144
+ @rs.should_not_include Time.utc(2006,6,14)
145
+ @rs.should_not_include Time.utc(2006,7,2)
146
+ end
147
+
148
+ specify "should not include times with no matching component" do
149
+ @rs.should_not_include Time.utc(2006,6,14,11,8)
150
+ @rs.should_not_include Time.utc(2006,7,2,5,30)
151
+ end
152
+
153
+ specify "should find the next date" do
154
+ @rs.find_next(Time.utc(2006,3,4)).should == Time.utc(2006,3,15)
155
+ @rs.find_next(Time.utc(2006,3,17)).should == Time.utc(2006,4,1)
156
+ end
157
+
158
+ end
159
+
160
+ context "A bi-monthly Schedule with monthday params" do
161
+
162
+ setup do
163
+ @rs = Recurring::Schedule.new :unit => 'months', :frequency => 2, :anchor => Time.utc(2006,4,15,10,30), :monthdays => [10,18]
164
+ end
165
+
166
+ specify "should include the beginnings of matching days" do
167
+ @rs.should_include Time.utc(2006,6,10)
168
+ @rs.should_include Time.utc(2006,8,18)
169
+ end
170
+
171
+ specify "should not include the beginnings of non_matching days" do
172
+ @rs.should_not_include Time.utc(2006,6,11)
173
+ end
174
+
175
+ specify "should not include the beginning of the anchor day" do
176
+ @rs.should_not_include Time.utc(2006,4,15)
177
+ end
178
+
179
+ specify "should not include the anchor time" do
180
+ @rs.should_not_include Time.utc(2006,4,15,10,30)
181
+ end
182
+
183
+ specify "should find the next date when the start point doesn't match" do
184
+ @rs.find_next(Time.utc(2006,6,15)).should == Time.utc(2006,6,18)
185
+ end
186
+
187
+ specify "should find the next date" do
188
+ @rs.find_next(Time.utc(2006,5,4)).should == Time.utc(2006,6,10)
189
+ @rs.find_next(Time.utc(2006,6,11)).should == Time.utc(2006,6,18)
190
+ end
191
+ end
192
+
193
+ context "A monthly schedule with week params" do
194
+ end
195
+
196
+ context "A third-monthly schedule with week params" do
197
+
198
+ setup do
199
+ lambda{@rs = Recurring::Schedule.new :unit => 'months', :week => 1, :frequency => 3}.should_raise ArgumentError
200
+ end
201
+
202
+ specify "should walk up the stairs" do
203
+ end
204
+
205
+ # should = *specify
206
+ #
207
+ # should "walk up the stairs" do
208
+ # @rs.should_include Time.utc(2007,11,14)
209
+ # end
210
+ end
211
+
212
+ context "A monthly schedule with week params and weekday params" do
213
+
214
+ setup do
215
+ @rs = Recurring::Schedule.new :unit => 'months', :weeks => 1, :weekdays => :monday
216
+ end
217
+
218
+ specify "should include the beginnings of matching days" do
219
+ @rs.should_include Time.utc(2006,12,4)
220
+ end
221
+
222
+ specify "should not include the matching days in off weeks" do
223
+ @rs.should_not_include Time.utc(2006,12,18)
224
+ @rs.should_not_include Time.utc(2006,11,27)
225
+ end
226
+
227
+ end
228
+
229
+ context "A monthly schedule with weekday params but no week params" do
230
+ end
231
+
232
+ context "A six-monthly schedule with week, weekday, and times params" do
233
+ end
234
+
235
+ #WEEKLY
236
+
237
+ context "A weekly schedule with weekday params" do
238
+
239
+ setup do
240
+ @rs = Recurring::Schedule.new :unit => 'weeks', :weekdays => %w{sunday monday weds friday}
241
+ end
242
+
243
+ specify "should include the beginnings of matching days" do
244
+ @rs.should_include Time.utc(2006,12,3)
245
+ @rs.should_include Time.utc(2006,12,6)
246
+ @rs.should_include Time.utc(2006,11,27)
247
+ end
248
+
249
+ specify "should not include the middle of matching days" do
250
+ @rs.should_not_include Time.utc(2006,12,6,10,30)
251
+ @rs.should_not_include Time.utc(2006,11,27,0,30)
252
+ @rs.should_not_include Time.utc(2006,12,7)
253
+ end
254
+
255
+ specify "should find the next date" do
256
+ @rs.find_next(Time.utc(2006,12,2)).should == Time.utc(2006,12,3)
257
+ @rs.find_next(Time.utc(2006,11,30)).should == Time.utc(2006,12,1)
258
+ end
259
+
260
+ end
261
+
262
+ context "A weekly schedule with only an anchor" do
263
+
264
+ setup do
265
+ @rs = Recurring::Schedule.new :unit => 'weeks', :anchor => Time.utc(2006,12,6,17,30)
266
+ end
267
+
268
+ specify "should include times that are offset by one week" do
269
+ @rs.should_include Time.utc(2006,12,20,17,30)
270
+ end
271
+
272
+ specify "should not include times that are not offset by one week" do
273
+ @rs.should_not_include Time.utc(2006,12,19,17,30)
274
+ @rs.should_not_include Time.utc(2006,12,20)
275
+ @rs.should_not_include Time.utc(2006,5,6,17,30)
276
+ end
277
+
278
+ specify "should find the next date" do
279
+ @rs.find_next(Time.utc(2006,5,4)).should == Time.utc(2006,5,10,17,30)
280
+ @rs.find_next(Time.utc(2006,6,11)).should == Time.utc(2006,6,14,17,30)
281
+ end
282
+ end
283
+
284
+ context "A third-weekly schedule with weekday and times params" do
285
+
286
+ setup do
287
+ @rs = Recurring::Schedule.new :unit => 'weeks', :frequency => 3, :anchor => Time.utc(2006,12,1), :weekdays => %w{mon fri sat}, :times => '3pm'
288
+ end
289
+
290
+ specify "should include the proper times on the right days of matching weeks" do
291
+ @rs.should_include Time.utc(2006,11,27,15)
292
+ @rs.should_include Time.utc(2006,12,1,15)
293
+ @rs.should_include Time.utc(2006,12,2,15)
294
+ @rs.should_include Time.utc(2006,12,18,15)
295
+ end
296
+
297
+ specify "should not include the beginnings of matching days" do
298
+ @rs.should_not_include Time.utc(2006,12,1)
299
+ end
300
+
301
+ specify "should not include the proper times on the right days of non-matching weeks" do
302
+ @rs.should_not_include Time.utc(2006,12,4,15)
303
+ end
304
+
305
+ specify "should find the next date" do
306
+ @rs.find_next(Time.utc(2006,12,4)).should == Time.utc(2006,12,18,15)
307
+ @rs.find_next(Time.utc(2006,12,18)).should == Time.utc(2006,12,18,15)
308
+ end
309
+
310
+ end
311
+
312
+ context "Recurring.week_of_year" do
313
+
314
+ specify "should return the same value for days in the same week" do
315
+ [[2006,12,1],
316
+ [2006,12,2],
317
+ [2006,11,28],
318
+ [2006,11,26]].collect do |args|
319
+ Time.utc *args
320
+ end.each do |t|
321
+ Recurring.week_of_year(t).should == 48
322
+ end
323
+
324
+ [[2006,12,7],
325
+ [2006,12,9],
326
+ [2006,12,3]].collect do |args|
327
+ Time.utc *args
328
+ end.each do |t|
329
+ Recurring.week_of_year(t).should == 49
330
+ end
331
+ end
332
+
333
+ end
334
+
335
+ context "A weekly schedule with times params but no days params" do
336
+
337
+ setup do
338
+ @rs = Recurring::Schedule.new :unit => 'weeks', :times => '4pm'
339
+ end
340
+
341
+ specify "should flip out hard" do
342
+ lambda{@rs.find_next(Time.utc(2006,1,1,16))}.should_raise RangeError
343
+ end
344
+
345
+ end
346
+
347
+ #DAILY
348
+
349
+ context "A daily schedule with no other params" do
350
+
351
+ setup do
352
+ @rs = Recurring::Schedule.new :unit => 'days'
353
+ end
354
+
355
+ specify "should match the beginning of every day" do
356
+ @rs.should_include Time.utc(2006,11,5)
357
+ @rs.should_include Time.mktime(2006,11,5)
358
+ end
359
+
360
+ specify "should not match the middle of any day" do
361
+ @rs.should_not_include Time.utc(2006,11,5,5)
362
+ @rs.should_not_include Time.utc(2006,11,5,0,0,22)
363
+ end
364
+
365
+ specify "should find the next date" do
366
+ @rs.find_next(Time.utc(2006,5,4,2)).should == Time.utc(2006,5,5)
367
+ @rs.find_next(Time.utc(2006,6,13,5)).should == Time.utc(2006,6,14)
368
+ end
369
+
370
+ end
371
+
372
+ context "A daily schedule with an anchor" do
373
+ setup do
374
+ @rs = Recurring::Schedule.new :unit => 'days', :anchor => Time.utc(2006,11,1,10,15,22)
375
+ end
376
+
377
+ specify "should include daily mulitples of the anchor" do
378
+ @rs.should_include Time.utc(2006,11,7,10,15,22)
379
+ end
380
+
381
+ specify "should not include other times in any day" do
382
+ @rs.should_not_include Time.utc(2006,11,7)
383
+ @rs.should_not_include Time.utc(2006,11,7,10,15,21)
384
+ end
385
+
386
+ specify "should find the next date" do
387
+ @rs.find_next(Time.utc(2006,5,4,12)).should == Time.utc(2006,5,5,10,15,22)
388
+ @rs.find_next(Time.utc(2006,6,13,5)).should == Time.utc(2006,6,13,10,15,22)
389
+ end
390
+ end
391
+
392
+ context "A fourth-daily schedule with only the unit and frequency != 1" do
393
+ specify "should have an ArgumentError" do
394
+ lambda{@rs = Recurring::Schedule.new :unit => 'days', :frequency => 4}.should_raise ArgumentError
395
+ end
396
+ end
397
+
398
+ context "A fourth-daily schedule with no other params" do
399
+
400
+ setup do
401
+ @rs = Recurring::Schedule.new :unit => 'days', :frequency => 4, :anchor => Time.utc(2006,11,1,9,30)
402
+ end
403
+
404
+ specify "should check the anchor" do
405
+ @rs.send(:check_anchor?).should == true
406
+ end
407
+
408
+ specify "should include the anchor" do
409
+ @rs.should_include Time.utc(2006,11,1,9,30)
410
+ end
411
+
412
+ specify "should not include the beginning of the anchor day" do
413
+ @rs.should_not_include Time.utc(2006,11,1)
414
+ end
415
+
416
+ specify "should include the time of the anchor, every four days" do
417
+ @rs.should_include Time.utc(2006,11,5,9,30)
418
+ end
419
+
420
+ specify "should not include the beginnings of matching days" do
421
+ @rs.should_not_include Time.utc(2006,11,5)
422
+ end
423
+
424
+ end
425
+
426
+ context "A daily schedule with times params" do
427
+ end
428
+
429
+ context "A third-daily schedule with times params" do
430
+ end
431
+
432
+ context "A daily schedule with monthday params" do
433
+ end
434
+
435
+ #HOURLY
436
+
437
+ context "An hourly schedule with no other params" do
438
+
439
+ setup do
440
+ @rs = Recurring::Schedule.new :unit => 'hours'
441
+ end
442
+
443
+ specify "should include the top of every hour" do
444
+ @rs.should_include Time.utc(2006,3,4,5)
445
+ end
446
+
447
+ specify "should not include times with non zero minutes or seconds" do
448
+ @rs.should_not_include Time.utc(2006,3,4,5,6)
449
+ @rs.should_not_include Time.utc(2006,3,4,5,0,6)
450
+ end
451
+
452
+ end
453
+
454
+ context "An hourly schedule with an anchor" do
455
+ setup do
456
+ @rs = Recurring::Schedule.new :unit => 'hours', :anchor => Time.utc(2001,5,15,11,17)
457
+ end
458
+
459
+ specify "should include any time with the same sub hour parts" do
460
+ @rs.should_include Time.utc(2006,5,15,14,17)
461
+ end
462
+
463
+ specify "should include the anchor time" do
464
+ end
465
+
466
+ specify "should not include any times with different sub hour parts" do
467
+ end
468
+
469
+ end
470
+
471
+ context "An bi-hourly schedule with time params" do
472
+
473
+ setup do
474
+ @rs = Recurring::Schedule.new :unit => 'hours', :frequency => 2, :times => '0:22 0:13:14', :anchor => Time.utc(2006)
475
+ end
476
+
477
+ specify "should find 4 times in 4 hours" do
478
+ should_find_in_range(@rs, 4, Time.utc(2006,12,12), Time.utc(2006,12,12,4) )
479
+ end
480
+
481
+ specify "should include times every other hour with the valid minutes and seconds" do
482
+ @rs.should_include Time.utc(2006,1,1,0,22)
483
+ @rs.should_include Time.utc(2006,1,1,0,13,14)
484
+ @rs.should_include Time.utc(2006,1,1,2,22)
485
+ @rs.should_include Time.utc(2006,1,1,16,13,14)
486
+ end
487
+
488
+ specify "should not include times with mismatched minutes or hours" do
489
+ @rs.should_not_include Time.utc(2006,12,12)
490
+ @rs.should_not_include Time.utc(2006,1,1,1,22)
491
+ @rs.should_not_include Time.utc(2006,1,1,3,13,14)
492
+ @rs.should_not_include Time.utc(2006,1,1,2,23)
493
+ @rs.should_not_include Time.utc(2006,1,1,16,13,24)
494
+ end
495
+
496
+ end
497
+
498
+ context "An hourly schedule with times params" do
499
+
500
+ setup do
501
+ @rs = Recurring::Schedule.new :unit => 'hours', :times => '0:15 4:30 0:45:30'
502
+ end
503
+
504
+ specify "should include matching times" do
505
+ @rs.should_include Time.utc(2001,11,11,11,15)
506
+ @rs.should_include Time.utc(2001,1,1,1,30)
507
+ @rs.should_include Time.utc(2009,12,14,3,45,30)
508
+ end
509
+
510
+ specify "should not include non matching times" do
511
+ @rs.should_not_include Time.utc(2001,11,11,11,15,05)
512
+ @rs.should_not_include Time.utc(2001,1,1,1,30,1)
513
+ @rs.should_not_include Time.utc(2001,1,1,1,35)
514
+ @rs.should_not_include Time.utc(2009,12,14,3,45)
515
+ end
516
+
517
+ specify "should find the next date" do
518
+ @rs.find_next(Time.utc(2006,5,4,12)).should == Time.utc(2006,5,4,12,15)
519
+ @rs.find_next(Time.utc(2006,6,13,5,31)).should == Time.utc(2006,6,13,5,45,30)
520
+ end
521
+
522
+ end
523
+
524
+ context "An eight-hourly schedule with times params" do
525
+ end
526
+
527
+ context "An hourly schedule with monthday params" do
528
+
529
+ specify "should ignore the monthday params" do
530
+ end
531
+
532
+ end
533
+
534
+ #MINUTELY
535
+
536
+ context "A minutely schedule with times params" do
537
+
538
+ end
539
+
540
+ context "A 5-minutely schedule with no other params" do
541
+
542
+ setup do
543
+ @rs = Recurring::Schedule.new :unit => 'minutes', :frequency => 5, :anchor => Time.utc(2006,9,1,10,30)
544
+ end
545
+
546
+ specify "should include a five minute multiples of the anchor time" do
547
+ @rs.should_include Time.utc(2006,9,1,10,30)
548
+ @rs.should_include Time.utc(2006,9,1,10,35)
549
+ @rs.should_include Time.utc(2006,9,1,20,30)
550
+ end
551
+
552
+ specify "should not include times with different seconds" do
553
+ @rs.should_not_include Time.utc(2006,9,1,10,30,30)
554
+ end
555
+
556
+ specify "should find the next date" do
557
+ @rs.find_next(Time.utc(2006,5,4,12,1)).should == Time.utc(2006,5,4,12,5)
558
+ @rs.find_next(Time.utc(2006,6,13,5,31)).should == Time.utc(2006,6,13,5,35)
559
+ end
560
+
561
+ specify "should ignore higher than second precision from the anchor" do
562
+ end
563
+
564
+ specify "should find 7 times in a 30 minute range" do
565
+ should_find_in_range(@rs, 7, Time.utc(2006,12,12,0,45), Time.utc(2006,12,12,1,15))
566
+ @rs.find_in_range( Time.utc(2006,12,12,0,45), Time.utc(2006,12,12,1,15) ).first.class.should_be Time
567
+ end
568
+
569
+ specify "should find in a range given as an object with first and last params" do
570
+ range = mock('range')
571
+ range.should_receive(:first).and_return(Time.utc(2006,12,12,0,45))
572
+ range.should_receive(:last).any_number_of_times.and_return(Time.utc(2006,12,12,1,15))
573
+ @rs.find_in_range(range).first.class.should_be Time
574
+ end
575
+
576
+ specify "should find in a range given as a Range" do
577
+ range = (Time.utc(2006,12,12,0,45)..Time.utc(2006,12,12,1,15))
578
+ @rs.find_in_range(range).first.class.should_be Time
579
+ end
580
+
581
+ specify "should find 10 times in a 45 minute range" do
582
+ should_find_in_range(@rs, 10, Time.utc(2006,12,12,1), Time.utc(2006,12,12,1,45))
583
+ @rs.find_in_range( Time.utc(2006,12,12,0,45), Time.utc(2006,12,12,1,15) ).first.class.should_be Time
584
+ end
585
+ end
586
+
587
+ #SECONDLY
588
+
589
+
590
+ #ETC
591
+
592
+ context "a daily schedule with a time 4:29am" do
593
+ setup do
594
+ @rs = Recurring::Schedule.new :unit => 'days', :times => '4:29am'
595
+ end
596
+ specify "should include any day at the time specified" do
597
+ @rs.should_include Time.utc(2001,3,4,4,29)
598
+ end
599
+ specify "should not include times other than 4:29am" do
600
+ @rs.should_not_include Time.utc(2004,5,17,1,39)
601
+ end
602
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: recurring
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.0
7
+ date: 2006-12-10 00:00:00 -08:00
8
+ summary: A scheduling library for recurring events
9
+ require_paths:
10
+ - lib
11
+ email: ryand-ruby@zenspider.com
12
+ homepage: " by Chris Anderson"
13
+ rubyforge_project: recurring
14
+ description: "Schedules can be like: * Every Tuesday at 5pm * The 1st and 15th of every other month * Every 2 hours * The first Friday of every month at 11:15am and 4:45:15pm == FEATURES/PROBLEMS: = Features * Fast searching of long ranges for matching times. * Ability to define Schedules in a few flexible ways. * Extensive RSpec specifications (run \"rake spec\" to see them) * Extensive RSpec specifications (run \"rake spec\" to see them) = Problems / Todo * Untested timezone support * Plans to offer complex Schedule and Masks == SYNOPSYS:"
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ authors:
29
+ - Ryan Davis
30
+ files:
31
+ - History.txt
32
+ - Manifest.txt
33
+ - README.txt
34
+ - Rakefile
35
+ - lib/recurring.rb
36
+ - spec/recurring_spec.rb
37
+ test_files: []
38
+
39
+ rdoc_options: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ executables: []
44
+
45
+ extensions: []
46
+
47
+ requirements: []
48
+
49
+ dependencies:
50
+ - !ruby/object:Gem::Dependency
51
+ name: hoe
52
+ version_requirement:
53
+ version_requirements: !ruby/object:Gem::Version::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: 1.1.6
58
+ version: