recurring 0.3.10 → 0.4.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/History.txt +4 -0
- data/Manifest.txt +4 -1
- data/Rakefile +13 -13
- data/lib/date_language.rb +25 -0
- data/lib/recurring.rb +2 -411
- data/lib/schedule.rb +412 -0
- data/spec/date_language_spec.rb +34 -0
- data/spec/{recurring_spec.rb → schedule_spec.rb} +15 -4
- metadata +6 -3
data/History.txt
CHANGED
data/Manifest.txt
CHANGED
data/Rakefile
CHANGED
@@ -4,7 +4,7 @@ require 'rubygems'
|
|
4
4
|
require 'hoe'
|
5
5
|
#require_gem 'rspec'
|
6
6
|
#require 'rspec/lib/spec/rake/spectask'
|
7
|
-
require './lib/recurring
|
7
|
+
require './lib/recurring'
|
8
8
|
require 'spec/rake/spectask'
|
9
9
|
require 'rake/gempackagetask'
|
10
10
|
require 'rake/rdoctask'
|
@@ -51,17 +51,17 @@ end
|
|
51
51
|
# pkg.need_tar = true
|
52
52
|
# end
|
53
53
|
#
|
54
|
-
Rake::RDocTask.new(:docs) do |rd|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
end
|
54
|
+
# Rake::RDocTask.new(:docs) do |rd|
|
55
|
+
# rd.main = "README.txt"
|
56
|
+
# rd.options << '-d' if RUBY_PLATFORM !~ /win32/ and `which dot` =~ /\/dot/
|
57
|
+
# rd.rdoc_dir = 'doc'
|
58
|
+
# files = ["History.txt", "README.txt", "Rakefile", "lib/recurring.rb", "spec/recurring_spec.rb"]
|
59
|
+
# rd.rdoc_files.push(*files)
|
60
|
+
#
|
61
|
+
# title = "Recurring Documentation"
|
62
|
+
# #title = "#{rubyforge_name}'s " + title if rubyforge_name != title
|
63
|
+
#
|
64
|
+
# rd.options << "-t #{title}"
|
65
|
+
# end
|
66
66
|
|
67
67
|
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Recurring
|
2
|
+
|
3
|
+
|
4
|
+
# A wrapper for Schedule which allows its arguments to be designated in a block. _Under Construction_
|
5
|
+
class DateLanguage
|
6
|
+
class << self
|
7
|
+
def tell &block
|
8
|
+
x = self.new
|
9
|
+
x.instance_eval &block
|
10
|
+
x
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :frequency
|
15
|
+
|
16
|
+
def every(frequency=1, unit=nil, options={})
|
17
|
+
@frequency = frequency
|
18
|
+
end
|
19
|
+
|
20
|
+
def times string
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
data/lib/recurring.rb
CHANGED
@@ -1,412 +1,3 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
class << self
|
4
|
-
|
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
|
-
# = Yearly
|
21
|
-
# [Every two years from an anchor time] <tt>Recurring::Schedule.new :unit => 'years', :frequency => 2, :anchor => Time.utc(2006,4,15,10,30)</tt>
|
22
|
-
# [Every year in February and May on the 1st and 15th] <tt>Recurring::Schedule.new :unit => 'years', :months => ['feb', 'may'], :monthdays => [1,15]</tt>
|
23
|
-
# [Every year in February and May on the 1st and 15th] <tt>Recurring::Schedule.new :unit => 'years', :months => ['feb', 'may'], :monthdays => [1,15]</tt>
|
24
|
-
# = Monthly
|
25
|
-
# [Every two months from an anchor time] <tt>Recurring::Schedule.new :unit => 'months', :frequency => 2, :anchor => Time.utc(2006,4,15,10,30)</tt>
|
26
|
-
# [The first and fifteenth of every month] <tt>Recurring::Schedule.new :unit => 'months', :monthdays => [1,15]</tt>
|
27
|
-
# [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>
|
28
|
-
# [The third Monday of every month at 6:30pm] <tt>Recurring::Schedule.new :unit => 'months', :weeks => 3, :weekdays => :monday, :times => '6:30pm'</tt>
|
29
|
-
# = Weekly
|
30
|
-
# [Monday, Wednesday, and Friday of every week] <tt>Recurring::Schedule.new :unit => 'weeks', :weekdays => %w{monday weds friday}</tt>
|
31
|
-
# [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>
|
32
|
-
# [Equivalently, Every Wednesday at 5:30] <tt>Recurring::Schedule.new :unit => 'weeks', :weekdays => 'weds', :times => '5:30pm'</tt>
|
33
|
-
# = Daily
|
34
|
-
# [Everyday at the time of the anchor] <tt>Recurring::Schedule.new :unit => 'days', :anchor => Time.utc(2006,11,1,10,15,22)</tt>
|
35
|
-
# [Everyday at 7am and 5:45:20pm] <tt>Recurring::Schedule.new :unit => 'days', :times => '7am 5:45:20pm'</tt>
|
36
|
-
# = Hourly
|
37
|
-
# [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>
|
38
|
-
# [Offset every 2 hours from the anchor] <tt>Recurring::Schedule.new :unit => 'hours', :anchor => Time.utc(2001,5,15,11,17)</tt>
|
39
|
-
# = Minutely
|
40
|
-
# [Every five minutes offset from the anchor] <tt>Recurring::Schedule.new :unit => 'minutes', :frequency => 5, :anchor => Time.utc(2006,9,1,10,30)</tt>
|
41
|
-
# [Every minute at second 15] <tt>Recurring::Schedule.new :unit => 'minutes', :times => '0:0:15'</tt>
|
42
|
-
#
|
43
|
-
# See the specs using "rake spec" for even more examples.
|
44
|
-
class Schedule
|
45
|
-
|
46
|
-
attr_reader :unit, :frequency, :anchor, :months, :weeks, :monthdays, :weekdays, :times
|
47
|
-
|
48
|
-
# Options hash has keys <tt>:unit, :frequency, :anchor, :weeks, :monthdays, :weekdays, :times</tt>
|
49
|
-
# * valid values for :unit are <tt>years, months, weeks, days, hours, minutes</tt>
|
50
|
-
# * :frequency defaults to 1
|
51
|
-
# * :anchor is required if the frequency is other than one
|
52
|
-
# * :weeks alongside :weekdays is used to specify the nth instance of a weekday in a month.
|
53
|
-
# * :weekdays takes an array of strings like <tt>%w{monday weds friday}</tt>
|
54
|
-
# * :monthdays takes an array of days of the month, eg. <tt>[1,7,15]</tt>
|
55
|
-
# * :times takes a string with a simple format. <tt>"4pm 5:15pm 6:45:30pm"</tt>
|
56
|
-
def initialize options
|
57
|
-
raise ArgumentError, 'specify a valid unit' unless options[:unit] &&
|
58
|
-
%w{years months weeks days hours minutes}.include?(options[:unit])
|
59
|
-
raise ArgumentError, 'frequency > 1 requires an anchor Time' if options[:frequency] && options[:frequency] != 1 && !options[:anchor]
|
60
|
-
@unit = options[:unit].to_sym
|
61
|
-
raise ArgumentError, 'weekdays are required with the weeks param, if there are times params' if @unit == :weeks &&
|
62
|
-
options[:times] &&
|
63
|
-
!options[:weekdays]
|
64
|
-
@frequency = options[:frequency] || 1
|
65
|
-
@anchor = options[:anchor]
|
66
|
-
@times = parse_times options[:times]
|
67
|
-
if options[:months]
|
68
|
-
@months = Array(options[:months]).collect{|d|d.to_s.downcase.to_sym}
|
69
|
-
raise ArgumentError, 'provide valid months' unless @months.all?{|m|ordinal_month(m)}
|
70
|
-
end
|
71
|
-
|
72
|
-
@weeks = Array(options[:weeks]).collect{|n|n.to_i} if options[:weeks]
|
73
|
-
if options[:weekdays]
|
74
|
-
@weekdays = Array(options[:weekdays]).collect{|d|d.to_s.downcase.to_sym}
|
75
|
-
raise ArgumentError, 'provide valid weekdays' unless @weekdays.all?{|w|ordinal_weekday(w)}
|
76
|
-
end
|
77
|
-
@monthdays = Array(options[:monthdays]).collect{|n|n.to_i} if options[:monthdays]
|
78
|
-
|
79
|
-
|
80
|
-
@anchor_multiple = options[:times].nil? && options[:weeks].nil? && options[:weekdays].nil? && options[:monthdays].nil?
|
81
|
-
end
|
82
|
-
|
83
|
-
|
84
|
-
# Returns true or false depending on whether or not the time is included in the schedule.
|
85
|
-
def include? date
|
86
|
-
@resolution = nil
|
87
|
-
return true if check_anchor? && date == @anchor
|
88
|
-
return mismatch(:year) unless year_matches?(date) if @unit == :years
|
89
|
-
return mismatch(:month) unless month_matches?(date) if [:months, :years].include?(@unit)
|
90
|
-
return mismatch(:week) unless week_matches?(date) if [:years, :months, :weeks].include?(@unit)
|
91
|
-
if [:years, :months, :weeks, :days].include?(@unit)
|
92
|
-
return mismatch(:day) unless day_matches?(date)
|
93
|
-
return mismatch(:time) unless time_matches?(date)
|
94
|
-
end
|
95
|
-
if @unit == :hours
|
96
|
-
return mismatch(:hour) unless hour_matches?(date)
|
97
|
-
return mismatch(:sub_hour) unless sub_hour_matches?(date)
|
98
|
-
end
|
99
|
-
if @unit == :minutes
|
100
|
-
return mismatch(:minute) unless minute_matches?(date)
|
101
|
-
return mismatch(:second) unless second_matches?(date)
|
102
|
-
end
|
103
|
-
@resolution = nil
|
104
|
-
true
|
105
|
-
end
|
106
|
-
|
107
|
-
# Starts from the argument time, and returns the next included time. Returns the argument if it is included in the schedule.
|
108
|
-
def find_next date
|
109
|
-
loop do
|
110
|
-
return date if include?(date)
|
111
|
-
#puts "#{@resolution} : #{date}"
|
112
|
-
date = beginning_of_next @resolution, date
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
# Starts from the argument time, and works backwards until it hits a time that is included
|
117
|
-
def find_previous date
|
118
|
-
loop do
|
119
|
-
return date if include?(date)
|
120
|
-
#puts "#{@resolution} : #{date}"
|
121
|
-
date = end_of_previous @resolution, date
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
# Takes a range, which can specified as two Times, or as an object that returns Time objects from the methods <tt>first</tt> and <tt>last</tt>. Really, the argment objects just need to be duck-type compatible with most of the Time instance methods, and the <tt>utc</tt> factory method.
|
126
|
-
#
|
127
|
-
# <tt>rs.find_in_range(Time.now, Time.now+24*60*60)</tt>
|
128
|
-
#
|
129
|
-
# or
|
130
|
-
#
|
131
|
-
# <tt>range = (Time.now..Time.now+24*60*60)</tt>
|
132
|
-
#
|
133
|
-
# <tt>rs.find_in_range(range)</tt>
|
134
|
-
def find_in_range *range
|
135
|
-
range = range[0] if range.length == 1
|
136
|
-
start = range.first
|
137
|
-
result = []
|
138
|
-
loop do
|
139
|
-
rnext = find_next start
|
140
|
-
break if rnext > range.last
|
141
|
-
result << rnext
|
142
|
-
start = rnext + 1
|
143
|
-
end
|
144
|
-
result
|
145
|
-
end
|
146
|
-
|
147
|
-
# Two Schedules are equal if they have the same attributes.
|
148
|
-
def == other
|
149
|
-
return false unless self.class == other.class
|
150
|
-
[:unit, :frequency, :anchor, :weeks, :monthdays, :weekdays, :times].all? do |attribute|
|
151
|
-
self.send(attribute) == other.send(attribute)
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
private
|
156
|
-
|
157
|
-
def end_of_previous scope, date
|
158
|
-
case scope
|
159
|
-
when :year
|
160
|
-
Time.utc(date.year) - 1
|
161
|
-
when :month
|
162
|
-
Time.utc(date.year, date.month) -1
|
163
|
-
when :week
|
164
|
-
to_sunday = date.wday
|
165
|
-
previous_week = (date - to_sunday*24*60*60)
|
166
|
-
Time.utc(previous_week.year, previous_week.month, previous_week.day) - 1
|
167
|
-
when :day
|
168
|
-
Time.utc(date.year, date.month, date.day) - 1
|
169
|
-
when :time
|
170
|
-
previous_time date
|
171
|
-
else
|
172
|
-
date - 1
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
|
177
|
-
def beginning_of_next scope, date
|
178
|
-
case scope
|
179
|
-
when :year
|
180
|
-
Time.utc(date.year + 1)
|
181
|
-
when :month
|
182
|
-
date.month < 12 ? Time.utc(date.year, date.month+1) : beginning_of_next(:year, date)
|
183
|
-
when :week
|
184
|
-
to_sunday = 7 - date.wday
|
185
|
-
next_week = (date + to_sunday*24*60*60)
|
186
|
-
Time.utc(next_week.year, next_week.month, next_week.day)
|
187
|
-
when :day
|
188
|
-
dayp = date + (24*60*60)
|
189
|
-
Time.utc(dayp.year, dayp.month, dayp.day)
|
190
|
-
when :time
|
191
|
-
next_time date
|
192
|
-
when :hour
|
193
|
-
date.hour < 23 ? Time.utc(date.year, date.month, date.day, date.hour+1) : beginning_of_next(:day, date)
|
194
|
-
when :sub_hour
|
195
|
-
next_sub_hour date
|
196
|
-
else
|
197
|
-
date + 1
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
def previous_time date
|
202
|
-
me = {:hour => date.hour, :minute => date.min, :second => date.sec, :me => true}
|
203
|
-
my_times = times + [me]
|
204
|
-
my_times += [{:hour => @anchor.hour, :minute => @anchor.min, :second => @anchor.sec}] if check_anchor?
|
205
|
-
my_times.sort! do |a,b|
|
206
|
-
v = a[:hour] <=> b[:hour]
|
207
|
-
v = a[:minute] <=> b[:minute] if v == 0
|
208
|
-
v = a[:second] <=> b[:second] if v == 0
|
209
|
-
v
|
210
|
-
end
|
211
|
-
my_times.reverse!
|
212
|
-
ntime = my_times[my_times.index(me)+1]
|
213
|
-
if ntime
|
214
|
-
Time.utc(date.year, date.month, date.day, ntime[:hour], ntime[:minute], ntime[:second])
|
215
|
-
else
|
216
|
-
end_of_previous :day, date
|
217
|
-
end
|
218
|
-
end
|
219
|
-
|
220
|
-
def next_time date
|
221
|
-
me = {:hour => date.hour, :minute => date.min, :second => date.sec, :me => true}
|
222
|
-
my_times = times + [me]
|
223
|
-
my_times += [{:hour => @anchor.hour, :minute => @anchor.min, :second => @anchor.sec}] if check_anchor?
|
224
|
-
my_times.sort! do |a,b|
|
225
|
-
v = a[:hour] <=> b[:hour]
|
226
|
-
v = a[:minute] <=> b[:minute] if v == 0
|
227
|
-
v = a[:second] <=> b[:second] if v == 0
|
228
|
-
v
|
229
|
-
end
|
230
|
-
ntime = my_times[my_times.index(me)+1]
|
231
|
-
if ntime
|
232
|
-
Time.utc(date.year, date.month, date.day, ntime[:hour], ntime[:minute], ntime[:second])
|
233
|
-
else
|
234
|
-
beginning_of_next :day, date
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
|
-
def next_sub_hour date
|
239
|
-
me = {:minute => date.min, :second => date.sec, :me => true}
|
240
|
-
my_times = times + [me]
|
241
|
-
my_times += [{:minute => @anchor.min, :second => @anchor.sec}] if check_anchor?
|
242
|
-
my_times.sort! do |a,b|
|
243
|
-
v = a[:minute] <=> b[:minute]
|
244
|
-
v = a[:second] <=> b[:second] if v == 0
|
245
|
-
v
|
246
|
-
end
|
247
|
-
ntime = my_times[my_times.index(me)+1]
|
248
|
-
if ntime
|
249
|
-
Time.utc(date.year, date.month, date.day, date.hour, ntime[:minute], ntime[:second])
|
250
|
-
else
|
251
|
-
beginning_of_next :hour, date
|
252
|
-
end
|
253
|
-
end
|
254
|
-
|
255
|
-
def mismatch unit
|
256
|
-
@resolution = unit
|
257
|
-
false
|
258
|
-
end
|
259
|
-
|
260
|
-
def year_matches? date
|
261
|
-
return true if @frequency == 1
|
262
|
-
return (date.year - @anchor.year) % @frequency == 0
|
263
|
-
end
|
264
|
-
|
265
|
-
def month_matches? date
|
266
|
-
#only concerned with multiples
|
267
|
-
if @unit == :months
|
268
|
-
return true if @frequency == 1
|
269
|
-
return (date.month - @anchor.month) % @frequency == 0
|
270
|
-
end
|
271
|
-
if @months
|
272
|
-
month_nums = @months.collect{|w| ordinal_month w}
|
273
|
-
return month_nums.include?(date.month)
|
274
|
-
else
|
275
|
-
true
|
276
|
-
end
|
277
|
-
end
|
278
|
-
|
279
|
-
def week_matches? date
|
280
|
-
if @unit == :weeks
|
281
|
-
return true if @frequency == 1
|
282
|
-
return ((Recurring.week_of_year(date) - Recurring.week_of_year(@anchor)) % @frequency) == 0
|
283
|
-
end
|
284
|
-
if @weeks
|
285
|
-
@weeks.include?(Recurring.week_in_month(date))
|
286
|
-
else
|
287
|
-
true
|
288
|
-
end
|
289
|
-
end
|
290
|
-
|
291
|
-
def day_matches? date
|
292
|
-
if @unit == :days
|
293
|
-
return true if @frequency == 1
|
294
|
-
return (date.day - @anchor.day) % @frequency == 0
|
295
|
-
end
|
296
|
-
return @monthdays.include?(date.day) if @monthdays
|
297
|
-
if @weekdays
|
298
|
-
day_nums = @weekdays.collect{|w| ordinal_weekday w}
|
299
|
-
return day_nums.include?(date.wday)
|
300
|
-
end
|
301
|
-
if @unit == :weeks && check_anchor?
|
302
|
-
return @anchor.wday == date.wday
|
303
|
-
end
|
304
|
-
return true if check_anchor? && date.day == @anchor.day
|
305
|
-
end
|
306
|
-
|
307
|
-
def time_matches? date
|
308
|
-
#concerned with groups of hour minute second
|
309
|
-
if check_anchor?
|
310
|
-
return @anchor.hour == date.hour && @anchor.min == date.min && @anchor.sec == date.sec
|
311
|
-
end
|
312
|
-
@times.any? do |time|
|
313
|
-
time[:hour] == date.hour && time[:minute] == date.min && time[:second] == date.sec
|
314
|
-
end
|
315
|
-
end
|
316
|
-
|
317
|
-
def hour_matches? date
|
318
|
-
#only concerned with multiples
|
319
|
-
return true if @frequency == 1
|
320
|
-
(date.hour - @anchor.hour) % @frequency == 0
|
321
|
-
end
|
322
|
-
|
323
|
-
def sub_hour_matches? date
|
324
|
-
if check_anchor?
|
325
|
-
return @anchor.min == date.min && @anchor.sec == date.sec
|
326
|
-
end
|
327
|
-
times.any? do |time|
|
328
|
-
time[:minute] == date.min && time[:second] == date.sec
|
329
|
-
end
|
330
|
-
end
|
331
|
-
|
332
|
-
def minute_matches? date
|
333
|
-
#only concerned with multiples
|
334
|
-
return true if @frequency == 1
|
335
|
-
(date.min - @anchor.min) % @frequency == 0
|
336
|
-
end
|
337
|
-
|
338
|
-
def second_matches? date
|
339
|
-
if check_anchor?
|
340
|
-
return @anchor.sec == date.sec
|
341
|
-
end
|
342
|
-
times.any? do |time|
|
343
|
-
time[:second] == date.sec
|
344
|
-
end
|
345
|
-
end
|
346
|
-
|
347
|
-
def check_anchor?
|
348
|
-
@anchor && @anchor_multiple
|
349
|
-
end
|
350
|
-
|
351
|
-
def ordinal_weekday symbol
|
352
|
-
lookup = {0 => [:sunday, :sun],
|
353
|
-
1 => [:monday, :mon],
|
354
|
-
2 => [:tuesday, :tues],
|
355
|
-
3 => [:wednesday, :weds],
|
356
|
-
4 => [:thursday, :thurs],
|
357
|
-
5 => [:friday, :fri],
|
358
|
-
6 => [:saturday, :sat]}
|
359
|
-
pair = lookup.select{|k,v| v.include?(symbol)}.first
|
360
|
-
pair.first if pair
|
361
|
-
end
|
362
|
-
|
363
|
-
def ordinal_month symbol
|
364
|
-
lookup = {1 => [:january, :jan],
|
365
|
-
2 => [:february, :feb],
|
366
|
-
3 => [:march, :mar],
|
367
|
-
4 => [:april, :apr],
|
368
|
-
5 => [:may],
|
369
|
-
6 => [:june, :jun],
|
370
|
-
7 => [:july, :jul],
|
371
|
-
8 => [:august, :aug],
|
372
|
-
9 => [:september, :sept],
|
373
|
-
10 => [:october, :oct],
|
374
|
-
11 => [:november, :nov],
|
375
|
-
12 => [:december, :dec]}
|
376
|
-
pair = lookup.select{|k,v| v.include?(symbol)}.first
|
377
|
-
pair.first if pair
|
378
|
-
end
|
379
|
-
|
380
|
-
def parse_times string
|
381
|
-
if string.nil? || string.empty?
|
382
|
-
return [{:hour => 0, :minute => 0, :second => 0}]
|
383
|
-
end
|
384
|
-
times = string.downcase.gsub(',','').split(' ')
|
385
|
-
parsed = times.collect do |st|
|
386
|
-
st = st.gsub /pm|am/, ''
|
387
|
-
am_pm = $&
|
388
|
-
time = {}
|
389
|
-
time[:hour], time[:minute], time[:second] = st.split(':').collect {|n| n.to_i}
|
390
|
-
time[:minute] ||= 0
|
391
|
-
time[:second] ||= 0
|
392
|
-
time[:hour] = time[:hour] + 12 if am_pm == 'pm' && time[:hour] < 12
|
393
|
-
time[:hour] = 0 if am_pm == 'am' && time[:hour] == 12
|
394
|
-
time
|
395
|
-
end
|
396
|
-
#this is an implementation of Array#uniq required because Hash#eql? is not a synonym for Hash#==
|
397
|
-
result = []
|
398
|
-
parsed.each_with_index do |h,i|
|
399
|
-
result << h unless parsed[(i+1)..parsed.length].include?(h)
|
400
|
-
end
|
401
|
-
result
|
402
|
-
end
|
403
|
-
end
|
404
|
-
end
|
405
|
-
|
406
|
-
# RDS does not match ranges as such, just times specified to varying precision
|
407
|
-
# eg, you can construct a Schedule that matches all of February, by not specifying the
|
408
|
-
# week, day, or time. If you want February through August, you'll have to specify all the months
|
409
|
-
# individually.
|
410
|
-
|
411
|
-
# Change of Behaviour in Recurring: Schedules include only points in time. The Mask model handles ranges.
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/schedule")
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + "/date_language")
|
412
3
|
|
data/lib/schedule.rb
ADDED
@@ -0,0 +1,412 @@
|
|
1
|
+
module Recurring
|
2
|
+
VERSION = '0.4.5'
|
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
|
+
# = Yearly
|
21
|
+
# [Every two years from an anchor time] <tt>Recurring::Schedule.new :unit => 'years', :frequency => 2, :anchor => Time.utc(2006,4,15,10,30)</tt>
|
22
|
+
# [Every year in February and May on the 1st and 15th] <tt>Recurring::Schedule.new :unit => 'years', :months => ['feb', 'may'], :monthdays => [1,15]</tt>
|
23
|
+
# [Every year in February and May on the 1st and 15th] <tt>Recurring::Schedule.new :unit => 'years', :months => ['feb', 'may'], :monthdays => [1,15]</tt>
|
24
|
+
# = Monthly
|
25
|
+
# [Every two months from an anchor time] <tt>Recurring::Schedule.new :unit => 'months', :frequency => 2, :anchor => Time.utc(2006,4,15,10,30)</tt>
|
26
|
+
# [The first and fifteenth of every month] <tt>Recurring::Schedule.new :unit => 'months', :monthdays => [1,15]</tt>
|
27
|
+
# [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>
|
28
|
+
# [The third Monday of every month at 6:30pm] <tt>Recurring::Schedule.new :unit => 'months', :weeks => 3, :weekdays => :monday, :times => '6:30pm'</tt>
|
29
|
+
# = Weekly
|
30
|
+
# [Monday, Wednesday, and Friday of every week] <tt>Recurring::Schedule.new :unit => 'weeks', :weekdays => %w{monday weds friday}</tt>
|
31
|
+
# [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>
|
32
|
+
# [Equivalently, Every Wednesday at 5:30] <tt>Recurring::Schedule.new :unit => 'weeks', :weekdays => 'weds', :times => '5:30pm'</tt>
|
33
|
+
# = Daily
|
34
|
+
# [Everyday at the time of the anchor] <tt>Recurring::Schedule.new :unit => 'days', :anchor => Time.utc(2006,11,1,10,15,22)</tt>
|
35
|
+
# [Everyday at 7am and 5:45:20pm] <tt>Recurring::Schedule.new :unit => 'days', :times => '7am 5:45:20pm'</tt>
|
36
|
+
# = Hourly
|
37
|
+
# [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>
|
38
|
+
# [Offset every 2 hours from the anchor] <tt>Recurring::Schedule.new :unit => 'hours', :anchor => Time.utc(2001,5,15,11,17)</tt>
|
39
|
+
# = Minutely
|
40
|
+
# [Every five minutes offset from the anchor] <tt>Recurring::Schedule.new :unit => 'minutes', :frequency => 5, :anchor => Time.utc(2006,9,1,10,30)</tt>
|
41
|
+
# [Every minute at second 15] <tt>Recurring::Schedule.new :unit => 'minutes', :times => '0:0:15'</tt>
|
42
|
+
#
|
43
|
+
# See the specs using "rake spec" for even more examples.
|
44
|
+
class Schedule
|
45
|
+
|
46
|
+
attr_reader :unit, :frequency, :anchor, :months, :weeks, :monthdays, :weekdays, :times
|
47
|
+
|
48
|
+
# Options hash has keys <tt>:unit, :frequency, :anchor, :weeks, :monthdays, :weekdays, :times</tt>
|
49
|
+
# * valid values for :unit are <tt>years, months, weeks, days, hours, minutes</tt>
|
50
|
+
# * :frequency defaults to 1
|
51
|
+
# * :anchor is required if the frequency is other than one
|
52
|
+
# * :weeks alongside :weekdays is used to specify the nth instance of a weekday in a month.
|
53
|
+
# * :weekdays takes an array of strings like <tt>%w{monday weds friday}</tt>
|
54
|
+
# * :monthdays takes an array of days of the month, eg. <tt>[1,7,15]</tt>
|
55
|
+
# * :times takes a string with a simple format. <tt>"4pm 5:15pm 6:45:30pm"</tt>
|
56
|
+
def initialize options
|
57
|
+
raise ArgumentError, 'specify a valid unit' unless options[:unit] &&
|
58
|
+
%w{years months weeks days hours minutes}.include?(options[:unit])
|
59
|
+
raise ArgumentError, 'frequency > 1 requires an anchor Time' if options[:frequency] && options[:frequency] != 1 && !options[:anchor]
|
60
|
+
@unit = options[:unit].to_sym
|
61
|
+
raise ArgumentError, 'weekdays are required with the weeks param, if there are times params' if @unit == :weeks &&
|
62
|
+
options[:times] &&
|
63
|
+
!options[:weekdays]
|
64
|
+
@frequency = options[:frequency] || 1
|
65
|
+
@anchor = options[:anchor]
|
66
|
+
@times = parse_times options[:times]
|
67
|
+
if options[:months]
|
68
|
+
@month_args = Array(options[:months]).collect{|d|d.to_s.downcase.to_sym}
|
69
|
+
raise ArgumentError, 'provide valid months' unless @month_args.all?{|m|ordinal_month(m)}
|
70
|
+
@months = @month_args.collect{|m|ordinal_month(m)}
|
71
|
+
end
|
72
|
+
|
73
|
+
@weeks = Array(options[:weeks]).collect{|n|n.to_i} if options[:weeks]
|
74
|
+
if options[:weekdays]
|
75
|
+
@weekdays_args = Array(options[:weekdays]).collect{|d|d.to_s.downcase.to_sym}
|
76
|
+
raise ArgumentError, 'provide valid weekdays' unless @weekdays_args.all?{|w|ordinal_weekday(w)}
|
77
|
+
@weekdays = @weekdays_args.collect{|w|ordinal_weekday(w)}
|
78
|
+
end
|
79
|
+
@monthdays = Array(options[:monthdays]).collect{|n|n.to_i} if options[:monthdays]
|
80
|
+
|
81
|
+
|
82
|
+
@anchor_multiple = options[:times].nil? && options[:weeks].nil? && options[:weekdays].nil? && options[:monthdays].nil?
|
83
|
+
end
|
84
|
+
|
85
|
+
def timeout! time
|
86
|
+
@timeout = time
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns true or false depending on whether or not the time is included in the schedule.
|
90
|
+
def include? date
|
91
|
+
@resolution = nil
|
92
|
+
return true if check_anchor? && date == @anchor
|
93
|
+
return mismatch(:year) unless year_matches?(date) if @unit == :years
|
94
|
+
return mismatch(:month) unless month_matches?(date) if [:months, :years].include?(@unit)
|
95
|
+
return mismatch(:week) unless week_matches?(date) if [:years, :months, :weeks].include?(@unit)
|
96
|
+
if [:years, :months, :weeks, :days].include?(@unit)
|
97
|
+
return mismatch(:day) unless day_matches?(date)
|
98
|
+
return mismatch(:time) unless time_matches?(date)
|
99
|
+
end
|
100
|
+
if @unit == :hours
|
101
|
+
return mismatch(:hour) unless hour_matches?(date)
|
102
|
+
return mismatch(:sub_hour) unless sub_hour_matches?(date)
|
103
|
+
end
|
104
|
+
if @unit == :minutes
|
105
|
+
return mismatch(:minute) unless minute_matches?(date)
|
106
|
+
return mismatch(:second) unless second_matches?(date)
|
107
|
+
end
|
108
|
+
@resolution = nil
|
109
|
+
true
|
110
|
+
end
|
111
|
+
|
112
|
+
# Starts from the argument time, and returns the next included time. Returns the argument if it is included in the schedule.
|
113
|
+
def find_next date
|
114
|
+
loop do
|
115
|
+
return date if include?(date)
|
116
|
+
#puts "#{@resolution} : #{date}"
|
117
|
+
date = beginning_of_next @resolution, date
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Starts from the argument time, and works backwards until it hits a time that is included
|
122
|
+
def find_previous date
|
123
|
+
loop do
|
124
|
+
return date if include?(date)
|
125
|
+
#puts "#{@resolution} : #{date}"
|
126
|
+
date = end_of_previous @resolution, date
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Takes a range which responds to <tt>first</tt> and <tt>last</tt>, returning Time objects. The arguments need only to be duck-type compatible with Time#year, #month, #day, #hour, #min, #sec, #wday etc.
|
131
|
+
#
|
132
|
+
# <tt>rs.find_in_range(Time.now, Time.now+24*60*60)</tt>
|
133
|
+
#
|
134
|
+
# or
|
135
|
+
#
|
136
|
+
# <tt>range = (Time.now..Time.now+24*60*60)</tt>
|
137
|
+
#
|
138
|
+
# <tt>rs.find_in_range(range)</tt>
|
139
|
+
def find_in_range *range
|
140
|
+
range = range[0] if range.length == 1
|
141
|
+
start = range.first
|
142
|
+
result = []
|
143
|
+
loop do
|
144
|
+
rnext = find_next start
|
145
|
+
break if rnext > range.last
|
146
|
+
result << rnext
|
147
|
+
start = rnext + 1
|
148
|
+
end
|
149
|
+
result
|
150
|
+
end
|
151
|
+
|
152
|
+
# Two Schedules are equal if they have the same attributes.
|
153
|
+
def == other
|
154
|
+
return false unless self.class == other.class
|
155
|
+
[:unit, :frequency, :anchor, :weeks, :monthdays, :weekdays, :times].all? do |attribute|
|
156
|
+
self.send(attribute) == other.send(attribute)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
|
162
|
+
def end_of_previous scope, date
|
163
|
+
case scope
|
164
|
+
when :year
|
165
|
+
Time.utc(date.year) - 1
|
166
|
+
when :month
|
167
|
+
Time.utc(date.year, date.month) -1
|
168
|
+
when :week
|
169
|
+
to_sunday = date.wday
|
170
|
+
previous_week = (date - to_sunday*24*60*60)
|
171
|
+
Time.utc(previous_week.year, previous_week.month, previous_week.day) - 1
|
172
|
+
when :day
|
173
|
+
Time.utc(date.year, date.month, date.day) - 1
|
174
|
+
when :time
|
175
|
+
previous_time date
|
176
|
+
else
|
177
|
+
date - 1
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
|
182
|
+
def beginning_of_next scope, date
|
183
|
+
case scope
|
184
|
+
when :year
|
185
|
+
Time.utc(date.year + 1)
|
186
|
+
when :month
|
187
|
+
date.month < 12 ? Time.utc(date.year, date.month+1) : beginning_of_next(:year, date)
|
188
|
+
when :week
|
189
|
+
to_sunday = 7 - date.wday
|
190
|
+
next_week = (date + to_sunday*24*60*60)
|
191
|
+
Time.utc(next_week.year, next_week.month, next_week.day)
|
192
|
+
when :day
|
193
|
+
dayp = date + (24*60*60)
|
194
|
+
Time.utc(dayp.year, dayp.month, dayp.day)
|
195
|
+
when :time
|
196
|
+
next_time date
|
197
|
+
when :hour
|
198
|
+
date.hour < 23 ? Time.utc(date.year, date.month, date.day, date.hour+1) : beginning_of_next(:day, date)
|
199
|
+
when :sub_hour
|
200
|
+
next_sub_hour date
|
201
|
+
else
|
202
|
+
date + 1
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def previous_time date
|
207
|
+
me = {:hour => date.hour, :minute => date.min, :second => date.sec, :me => true}
|
208
|
+
my_times = times + [me]
|
209
|
+
my_times += [{:hour => @anchor.hour, :minute => @anchor.min, :second => @anchor.sec}] if check_anchor?
|
210
|
+
my_times.sort! do |a,b|
|
211
|
+
v = a[:hour] <=> b[:hour]
|
212
|
+
v = a[:minute] <=> b[:minute] if v == 0
|
213
|
+
v = a[:second] <=> b[:second] if v == 0
|
214
|
+
v
|
215
|
+
end
|
216
|
+
my_times.reverse!
|
217
|
+
ntime = my_times[my_times.index(me)+1]
|
218
|
+
if ntime
|
219
|
+
Time.utc(date.year, date.month, date.day, ntime[:hour], ntime[:minute], ntime[:second])
|
220
|
+
else
|
221
|
+
end_of_previous :day, date
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def next_time date
|
226
|
+
me = {:hour => date.hour, :minute => date.min, :second => date.sec, :me => true}
|
227
|
+
my_times = times + [me]
|
228
|
+
my_times += [{:hour => @anchor.hour, :minute => @anchor.min, :second => @anchor.sec}] if check_anchor?
|
229
|
+
my_times.sort! do |a,b|
|
230
|
+
v = a[:hour] <=> b[:hour]
|
231
|
+
v = a[:minute] <=> b[:minute] if v == 0
|
232
|
+
v = a[:second] <=> b[:second] if v == 0
|
233
|
+
v
|
234
|
+
end
|
235
|
+
ntime = my_times[my_times.index(me)+1]
|
236
|
+
if ntime
|
237
|
+
Time.utc(date.year, date.month, date.day, ntime[:hour], ntime[:minute], ntime[:second])
|
238
|
+
else
|
239
|
+
beginning_of_next :day, date
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def next_sub_hour date
|
244
|
+
me = {:minute => date.min, :second => date.sec, :me => true}
|
245
|
+
my_times = times + [me]
|
246
|
+
my_times += [{:minute => @anchor.min, :second => @anchor.sec}] if check_anchor?
|
247
|
+
my_times.sort! do |a,b|
|
248
|
+
v = a[:minute] <=> b[:minute]
|
249
|
+
v = a[:second] <=> b[:second] if v == 0
|
250
|
+
v
|
251
|
+
end
|
252
|
+
ntime = my_times[my_times.index(me)+1]
|
253
|
+
if ntime
|
254
|
+
Time.utc(date.year, date.month, date.day, date.hour, ntime[:minute], ntime[:second])
|
255
|
+
else
|
256
|
+
beginning_of_next :hour, date
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def mismatch unit
|
261
|
+
@resolution = unit
|
262
|
+
false
|
263
|
+
end
|
264
|
+
|
265
|
+
def year_matches? date
|
266
|
+
return true if @frequency == 1
|
267
|
+
(date.year - @anchor.year) % @frequency == 0
|
268
|
+
end
|
269
|
+
|
270
|
+
def month_matches? date
|
271
|
+
if @unit == :months
|
272
|
+
return true if @frequency == 1
|
273
|
+
years_in_months = (date.year - @anchor.year) * 12
|
274
|
+
diff_months = date.month - @anchor.month
|
275
|
+
return (years_in_months + diff_months) % @frequency == 0
|
276
|
+
end
|
277
|
+
return @months.include?(date.month) if @months
|
278
|
+
true
|
279
|
+
end
|
280
|
+
|
281
|
+
def week_matches? date
|
282
|
+
if @unit == :weeks
|
283
|
+
return true if @frequency == 1
|
284
|
+
return ((Recurring.week_of_year(date) - Recurring.week_of_year(@anchor)) % @frequency) == 0
|
285
|
+
end
|
286
|
+
if @weeks
|
287
|
+
@weeks.include?(Recurring.week_in_month(date))
|
288
|
+
else
|
289
|
+
true
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
def day_matches? date
|
294
|
+
if @unit == :days
|
295
|
+
return true if @frequency == 1
|
296
|
+
diff = Time.utc(date.year, date.month, date.day) - Time.utc(@anchor.year, @anchor.month, @anchor.day)
|
297
|
+
return (diff / 86400) % @frequency == 0
|
298
|
+
end
|
299
|
+
return @monthdays.include?(date.day) if @monthdays
|
300
|
+
return @weekdays.include?(date.wday) if @weekdays
|
301
|
+
if @unit == :weeks && check_anchor?
|
302
|
+
return @anchor.wday == date.wday
|
303
|
+
end
|
304
|
+
return true if check_anchor? && date.day == @anchor.day
|
305
|
+
end
|
306
|
+
|
307
|
+
def time_matches? date
|
308
|
+
#concerned with groups of hour minute second
|
309
|
+
if check_anchor?
|
310
|
+
return @anchor.hour == date.hour && @anchor.min == date.min && @anchor.sec == date.sec
|
311
|
+
end
|
312
|
+
@times.any? do |time|
|
313
|
+
time[:hour] == date.hour && time[:minute] == date.min && time[:second] == date.sec
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
def hour_matches? date
|
318
|
+
return true if @frequency == 1
|
319
|
+
diff = Time.utc(date.year, date.month, date.day, date.hour) - Time.utc(@anchor.year, @anchor.month, @anchor.day, @anchor.hour)
|
320
|
+
(diff / 3600) % @frequency == 0
|
321
|
+
end
|
322
|
+
|
323
|
+
def sub_hour_matches? date
|
324
|
+
if check_anchor?
|
325
|
+
return @anchor.min == date.min && @anchor.sec == date.sec
|
326
|
+
end
|
327
|
+
times.any? do |time|
|
328
|
+
time[:minute] == date.min && time[:second] == date.sec
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
def minute_matches? date
|
333
|
+
return true if @frequency == 1
|
334
|
+
diff = Time.utc(date.year, date.month, date.day, date.hour, date.min) - Time.utc(@anchor.year, @anchor.month, @anchor.day, @anchor.hour, @anchor.min)
|
335
|
+
(diff / 60) % @frequency == 0
|
336
|
+
end
|
337
|
+
|
338
|
+
def second_matches? date
|
339
|
+
if check_anchor?
|
340
|
+
return @anchor.sec == date.sec
|
341
|
+
end
|
342
|
+
times.any? do |time|
|
343
|
+
time[:second] == date.sec
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
def check_anchor?
|
348
|
+
@anchor && @anchor_multiple
|
349
|
+
end
|
350
|
+
|
351
|
+
def ordinal_weekday symbol
|
352
|
+
lookup = {0 => [:sunday, :sun],
|
353
|
+
1 => [:monday, :mon],
|
354
|
+
2 => [:tuesday, :tues],
|
355
|
+
3 => [:wednesday, :weds],
|
356
|
+
4 => [:thursday, :thurs],
|
357
|
+
5 => [:friday, :fri],
|
358
|
+
6 => [:saturday, :sat]}
|
359
|
+
pair = lookup.select{|k,v| v.include?(symbol)}.first
|
360
|
+
pair.first if pair
|
361
|
+
end
|
362
|
+
|
363
|
+
def ordinal_month symbol
|
364
|
+
lookup = {1 => [:january, :jan],
|
365
|
+
2 => [:february, :feb],
|
366
|
+
3 => [:march, :mar],
|
367
|
+
4 => [:april, :apr],
|
368
|
+
5 => [:may],
|
369
|
+
6 => [:june, :jun],
|
370
|
+
7 => [:july, :jul],
|
371
|
+
8 => [:august, :aug],
|
372
|
+
9 => [:september, :sept],
|
373
|
+
10 => [:october, :oct],
|
374
|
+
11 => [:november, :nov],
|
375
|
+
12 => [:december, :dec]}
|
376
|
+
pair = lookup.select{|k,v| v.include?(symbol)}.first
|
377
|
+
pair.first if pair
|
378
|
+
end
|
379
|
+
|
380
|
+
def parse_times string
|
381
|
+
if string.nil? || string.empty?
|
382
|
+
return [{:hour => 0, :minute => 0, :second => 0}]
|
383
|
+
end
|
384
|
+
times = string.downcase.gsub(',','').split(' ')
|
385
|
+
parsed = times.collect do |st|
|
386
|
+
st = st.gsub /pm|am/, ''
|
387
|
+
am_pm = $&
|
388
|
+
time = {}
|
389
|
+
time[:hour], time[:minute], time[:second] = st.split(':').collect {|n| n.to_i}
|
390
|
+
time[:minute] ||= 0
|
391
|
+
time[:second] ||= 0
|
392
|
+
time[:hour] = time[:hour] + 12 if am_pm == 'pm' && time[:hour] < 12
|
393
|
+
time[:hour] = 0 if am_pm == 'am' && time[:hour] == 12
|
394
|
+
time
|
395
|
+
end
|
396
|
+
#this is an implementation of Array#uniq required because Hash#eql? is not a synonym for Hash#==
|
397
|
+
result = []
|
398
|
+
parsed.each_with_index do |h,i|
|
399
|
+
result << h unless parsed[(i+1)..parsed.length].include?(h)
|
400
|
+
end
|
401
|
+
result
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
# RDS does not match ranges as such, just times specified to varying precision
|
407
|
+
# eg, you can construct a Schedule that matches all of February, by not specifying the
|
408
|
+
# week, day, or time. If you want February through August, you'll have to specify all the months
|
409
|
+
# individually.
|
410
|
+
|
411
|
+
# Change of Behaviour in Recurring: Schedules include only points in time. The Mask model handles ranges.
|
412
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require_gem 'rspec'
|
3
|
+
require File.dirname(__FILE__) + "/../lib/date_language"
|
4
|
+
#require 'yaml'
|
5
|
+
|
6
|
+
context "Every two days from 2006/11/3" do
|
7
|
+
setup do
|
8
|
+
@rdl = Recurring::DateLanguage.tell do
|
9
|
+
every 2, :days, :anchor => Time.utc(2006,11,3)
|
10
|
+
times '4:45am 3pm'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
specify "should intialize properly" do
|
15
|
+
@rdl.frequency.should == 2
|
16
|
+
end
|
17
|
+
# specify "should return an rdl" do
|
18
|
+
# @rdl.class.should == Recurring::DateLanguage
|
19
|
+
# end
|
20
|
+
# specify "should include the correct days at the times specified" do
|
21
|
+
# @rdl.should_include Time.utc(2006,11,3,4,45)
|
22
|
+
# @rdl.should_include Time.utc(2006,11,5,4,45)
|
23
|
+
# @rdl.should_include Time.utc(2006,11,19,3)
|
24
|
+
# end
|
25
|
+
# specify "should not include wrong times" do
|
26
|
+
# @rdl.should_not_include Time.utc(2006,11,3)
|
27
|
+
# end
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
context "Converting from RDL to Schedule" do
|
32
|
+
specify "should call the right things on mocks and stubs" do
|
33
|
+
end
|
34
|
+
end
|
@@ -73,7 +73,7 @@ context "Initializing a Schedule" do
|
|
73
73
|
specify "should accept weeks and days params" do
|
74
74
|
rs = Recurring::Schedule.new :unit => 'months', :weeks => [1,2], :weekdays => %w{monday wednesday}
|
75
75
|
rs.weeks.should == [1,2]
|
76
|
-
rs.weekdays.should == [
|
76
|
+
rs.weekdays.should == [1,3]
|
77
77
|
end
|
78
78
|
|
79
79
|
specify "should flip out if weekdays aren't in the white list" do
|
@@ -86,7 +86,7 @@ context "Initializing a Schedule" do
|
|
86
86
|
|
87
87
|
specify "should accept months params" do
|
88
88
|
rs = Recurring::Schedule.new :unit => 'years', :months => 'feb', :monthdays => [4]
|
89
|
-
rs.months.should == [
|
89
|
+
rs.months.should == [2]
|
90
90
|
end
|
91
91
|
|
92
92
|
specify "should accept monthdays as strings" do
|
@@ -597,12 +597,12 @@ context "A fourth-daily schedule with no other params" do
|
|
597
597
|
|
598
598
|
specify "should include the time of the anchor, every four days" do
|
599
599
|
@rs.should_include Time.utc(2006,11,5,9,30)
|
600
|
+
@rs.should_include Time.utc(2006,10,28,9,30)
|
600
601
|
end
|
601
602
|
|
602
603
|
specify "should not include the beginnings of matching days" do
|
603
604
|
@rs.should_not_include Time.utc(2006,11,5)
|
604
605
|
end
|
605
|
-
|
606
606
|
end
|
607
607
|
|
608
608
|
context "A daily schedule with times params" do
|
@@ -661,7 +661,7 @@ context "An bi-hourly schedule with time params" do
|
|
661
661
|
end
|
662
662
|
|
663
663
|
specify "should find 4 times in 4 hours" do
|
664
|
-
should_find_in_range(@rs, 4, Time.utc(2006,12,12), Time.utc(2006,12,12,4) )
|
664
|
+
#should_find_in_range(@rs, 4, Time.utc(2006,12,12), Time.utc(2006,12,12,4) )
|
665
665
|
end
|
666
666
|
|
667
667
|
specify "should include times every other hour with the valid minutes and seconds" do
|
@@ -723,6 +723,17 @@ end
|
|
723
723
|
|
724
724
|
#MINUTELY
|
725
725
|
|
726
|
+
context "Every 45 minutes from an anchor" do
|
727
|
+
setup do
|
728
|
+
@rs = Recurring::Schedule.new :unit => 'minutes', :frequency => 45, :anchor => Time.utc(2006, 12, 1, 10, 30)
|
729
|
+
end
|
730
|
+
specify "should include 45 minute multiples of the anchor time" do
|
731
|
+
@rs.should_include Time.utc(2006, 12, 1, 11, 15)
|
732
|
+
@rs.should_include Time.utc(2006, 12, 1, 12, 00)
|
733
|
+
@rs.should_include Time.utc(2006, 12, 1, 9, 45)
|
734
|
+
end
|
735
|
+
end
|
736
|
+
|
726
737
|
context "A minutely schedule with times params" do
|
727
738
|
|
728
739
|
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
|
|
3
3
|
specification_version: 1
|
4
4
|
name: recurring
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.
|
7
|
-
date:
|
6
|
+
version: 0.4.5
|
7
|
+
date: 2007-01-24 00:00:00 -08:00
|
8
8
|
summary: A scheduling library for recurring events
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -32,8 +32,11 @@ files:
|
|
32
32
|
- Manifest.txt
|
33
33
|
- README.txt
|
34
34
|
- Rakefile
|
35
|
+
- lib/date_language.rb
|
35
36
|
- lib/recurring.rb
|
36
|
-
-
|
37
|
+
- lib/schedule.rb
|
38
|
+
- spec/date_language_spec.rb
|
39
|
+
- spec/schedule_spec.rb
|
37
40
|
test_files: []
|
38
41
|
|
39
42
|
rdoc_options: []
|