recurring 0.1.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.
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: