rufus-scheduler 2.0.24 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/CHANGELOG.txt +6 -0
  2. data/CREDITS.txt +4 -0
  3. data/README.md +1064 -0
  4. data/Rakefile +1 -4
  5. data/TODO.txt +145 -55
  6. data/lib/rufus/scheduler.rb +502 -26
  7. data/lib/rufus/{sc → scheduler}/cronline.rb +46 -17
  8. data/lib/rufus/{sc/version.rb → scheduler/job_array.rb} +56 -4
  9. data/lib/rufus/scheduler/jobs.rb +548 -0
  10. data/lib/rufus/scheduler/util.rb +318 -0
  11. data/rufus-scheduler.gemspec +30 -4
  12. data/spec/cronline_spec.rb +29 -8
  13. data/spec/error_spec.rb +116 -0
  14. data/spec/job_array_spec.rb +39 -0
  15. data/spec/job_at_spec.rb +58 -0
  16. data/spec/job_cron_spec.rb +67 -0
  17. data/spec/job_every_spec.rb +71 -0
  18. data/spec/job_in_spec.rb +20 -0
  19. data/spec/job_interval_spec.rb +68 -0
  20. data/spec/job_repeat_spec.rb +308 -0
  21. data/spec/job_spec.rb +387 -115
  22. data/spec/lockfile_spec.rb +61 -0
  23. data/spec/parse_spec.rb +203 -0
  24. data/spec/schedule_at_spec.rb +129 -0
  25. data/spec/schedule_cron_spec.rb +66 -0
  26. data/spec/schedule_every_spec.rb +109 -0
  27. data/spec/schedule_in_spec.rb +80 -0
  28. data/spec/schedule_interval_spec.rb +128 -0
  29. data/spec/scheduler_spec.rb +831 -124
  30. data/spec/spec_helper.rb +65 -0
  31. data/spec/threads_spec.rb +75 -0
  32. metadata +64 -59
  33. data/README.rdoc +0 -661
  34. data/lib/rufus/otime.rb +0 -3
  35. data/lib/rufus/sc/jobqueues.rb +0 -160
  36. data/lib/rufus/sc/jobs.rb +0 -471
  37. data/lib/rufus/sc/rtime.rb +0 -363
  38. data/lib/rufus/sc/scheduler.rb +0 -636
  39. data/spec/at_in_spec.rb +0 -47
  40. data/spec/at_spec.rb +0 -125
  41. data/spec/blocking_spec.rb +0 -64
  42. data/spec/cron_spec.rb +0 -134
  43. data/spec/every_spec.rb +0 -304
  44. data/spec/exception_spec.rb +0 -113
  45. data/spec/in_spec.rb +0 -150
  46. data/spec/mutex_spec.rb +0 -159
  47. data/spec/rtime_spec.rb +0 -137
  48. data/spec/schedulable_spec.rb +0 -97
  49. data/spec/spec_base.rb +0 -87
  50. data/spec/stress_schedule_unschedule_spec.rb +0 -159
  51. data/spec/timeout_spec.rb +0 -148
  52. data/test/kjw.rb +0 -113
  53. data/test/t.rb +0 -20
@@ -0,0 +1,318 @@
1
+ #--
2
+ # Copyright (c) 2006-2013, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+
26
+ module Rufus
27
+
28
+ class Scheduler
29
+
30
+ #--
31
+ # time and string methods
32
+ #++
33
+
34
+ def self.parse(o)
35
+
36
+ opts = { :no_error => true }
37
+
38
+ parse_cron(o, opts) ||
39
+ parse_in(o, opts) || # covers 'every' schedule strings
40
+ parse_at(o, opts) ||
41
+ raise(ArgumentError.new("couldn't parse \"#{o}\""))
42
+ end
43
+
44
+ def self.parse_in(o, opts={})
45
+
46
+ o.is_a?(String) ? parse_duration(o, opts) : o
47
+ end
48
+
49
+ TZ_REGEX = /\b((?:[a-zA-Z][a-zA-z0-9\-+]+)(?:\/[a-zA-Z0-9\-+]+)?)\b/
50
+
51
+ def self.parse_at(o, opts={})
52
+
53
+ return o if o.is_a?(Time)
54
+
55
+ tz = nil
56
+ s =
57
+ o.to_s.gsub(TZ_REGEX) { |m|
58
+ t = TZInfo::Timezone.get(m) rescue nil
59
+ tz ||= t
60
+ t ? '' : m
61
+ }
62
+
63
+ begin
64
+ DateTime.parse(o)
65
+ rescue
66
+ raise ArgumentError, "no time information in #{o.inspect}"
67
+ end if RUBY_VERSION < '1.9.0'
68
+
69
+ t = Time.parse(s)
70
+
71
+ t = tz.local_to_utc(t) if tz
72
+
73
+ t
74
+
75
+ rescue StandardError => se
76
+
77
+ return nil if opts[:no_error]
78
+ raise se
79
+ end
80
+
81
+ def self.parse_cron(o, opts)
82
+
83
+ CronLine.new(o)
84
+
85
+ rescue ArgumentError => ae
86
+
87
+ return nil if opts[:no_error]
88
+ raise ae
89
+ end
90
+
91
+ def self.parse_to_time(o)
92
+
93
+ t = o
94
+ t = parse(t) if t.is_a?(String)
95
+ t = Time.now + t if t.is_a?(Numeric)
96
+
97
+ raise ArgumentError.new(
98
+ "cannot turn #{o.inspect} to a point in time, doesn't make sense"
99
+ ) unless t.is_a?(Time)
100
+
101
+ t
102
+ end
103
+
104
+ DURATIONS2M = [
105
+ [ 'y', 365 * 24 * 3600 ],
106
+ [ 'M', 30 * 24 * 3600 ],
107
+ [ 'w', 7 * 24 * 3600 ],
108
+ [ 'd', 24 * 3600 ],
109
+ [ 'h', 3600 ],
110
+ [ 'm', 60 ],
111
+ [ 's', 1 ]
112
+ ]
113
+ DURATIONS2 = DURATIONS2M.dup
114
+ DURATIONS2.delete_at(1)
115
+
116
+ DURATIONS = DURATIONS2M.inject({}) { |r, (k, v)| r[k] = v; r }
117
+ DURATION_LETTERS = DURATIONS.keys.join
118
+
119
+ DU_KEYS = DURATIONS2M.collect { |k, v| k.to_sym }
120
+
121
+ # Turns a string like '1m10s' into a float like '70.0', more formally,
122
+ # turns a time duration expressed as a string into a Float instance
123
+ # (millisecond count).
124
+ #
125
+ # w -> week
126
+ # d -> day
127
+ # h -> hour
128
+ # m -> minute
129
+ # s -> second
130
+ # M -> month
131
+ # y -> year
132
+ # 'nada' -> millisecond
133
+ #
134
+ # Some examples:
135
+ #
136
+ # Rufus::Scheduler.parse_duration_string "0.5" # => 0.5
137
+ # Rufus::Scheduler.parse_duration_string "500" # => 0.5
138
+ # Rufus::Scheduler.parse_duration_string "1000" # => 1.0
139
+ # Rufus::Scheduler.parse_duration_string "1h" # => 3600.0
140
+ # Rufus::Scheduler.parse_duration_string "1h10s" # => 3610.0
141
+ # Rufus::Scheduler.parse_duration_string "1w2d" # => 777600.0
142
+ #
143
+ # Negative time strings are OK (Thanks Danny Fullerton):
144
+ #
145
+ # Rufus::Scheduler.parse_duration_string "-0.5" # => -0.5
146
+ # Rufus::Scheduler.parse_duration_string "-1h" # => -3600.0
147
+ #
148
+ def self.parse_duration(string, opts={})
149
+
150
+ string = string.to_s
151
+
152
+ return 0.0 if string == ''
153
+
154
+ m = string.match(/^(-?)([\d\.#{DURATION_LETTERS}]+)$/)
155
+
156
+ return nil if m.nil? && opts[:no_error]
157
+ raise ArgumentError.new("cannot parse '#{string}'") if m.nil?
158
+
159
+ mod = m[1] == '-' ? -1.0 : 1.0
160
+ val = 0.0
161
+
162
+ s = m[2]
163
+
164
+ while s.length > 0
165
+ m = nil
166
+ if m = s.match(/^(\d+|\d+\.\d*|\d*\.\d+)([#{DURATION_LETTERS}])(.*)$/)
167
+ val += m[1].to_f * DURATIONS[m[2]]
168
+ elsif s.match(/^\d+$/)
169
+ val += s.to_i
170
+ elsif s.match(/^\d*\.\d*$/)
171
+ val += s.to_f
172
+ elsif opts[:no_error]
173
+ return nil
174
+ else
175
+ raise ArgumentError.new(
176
+ "cannot parse '#{string}' (especially '#{s}')"
177
+ )
178
+ end
179
+ break unless m && m[3]
180
+ s = m[3]
181
+ end
182
+
183
+ mod * val
184
+ end
185
+
186
+ # Turns a number of seconds into a a time string
187
+ #
188
+ # Rufus.to_duration_string 0 # => '0s'
189
+ # Rufus.to_duration_string 60 # => '1m'
190
+ # Rufus.to_duration_string 3661 # => '1h1m1s'
191
+ # Rufus.to_duration_string 7 * 24 * 3600 # => '1w'
192
+ # Rufus.to_duration_string 30 * 24 * 3600 + 1 # => "4w2d1s"
193
+ #
194
+ # It goes from seconds to the year. Months are not counted (as they
195
+ # are of variable length). Weeks are counted.
196
+ #
197
+ # For 30 days months to be counted, the second parameter of this
198
+ # method can be set to true.
199
+ #
200
+ # Rufus.to_time_string 30 * 24 * 3600 + 1, true # => "1M1s"
201
+ #
202
+ # (to_time_string is an alias for to_duration_string)
203
+ #
204
+ # If a Float value is passed, milliseconds will be displayed without
205
+ # 'marker'
206
+ #
207
+ # Rufus.to_duration_string 0.051 # => "51"
208
+ # Rufus.to_duration_string 7.051 # => "7s51"
209
+ # Rufus.to_duration_string 0.120 + 30 * 24 * 3600 + 1 # => "4w2d1s120"
210
+ #
211
+ # (this behaviour mirrors the one found for parse_time_string()).
212
+ #
213
+ # Options are :
214
+ #
215
+ # * :months, if set to true, months (M) of 30 days will be taken into
216
+ # account when building up the result
217
+ # * :drop_seconds, if set to true, seconds and milliseconds will be trimmed
218
+ # from the result
219
+ #
220
+ def self.to_duration(seconds, options={})
221
+
222
+ h = to_duration_hash(seconds, options)
223
+
224
+ return (options[:drop_seconds] ? '0m' : '0s') if h.empty?
225
+
226
+ s =
227
+ DU_KEYS.inject('') { |r, key|
228
+ count = h[key]
229
+ count = nil if count == 0
230
+ r << "#{count}#{key}" if count
231
+ r
232
+ }
233
+
234
+ ms = h[:ms]
235
+ s << ms.to_s if ms
236
+
237
+ s
238
+ end
239
+
240
+ class << self
241
+ alias to_duration_string to_duration
242
+ end
243
+
244
+ # Turns a number of seconds (integer or Float) into a hash like in :
245
+ #
246
+ # Rufus.to_duration_hash 0.051
247
+ # # => { :ms => "51" }
248
+ # Rufus.to_duration_hash 7.051
249
+ # # => { :s => 7, :ms => "51" }
250
+ # Rufus.to_duration_hash 0.120 + 30 * 24 * 3600 + 1
251
+ # # => { :w => 4, :d => 2, :s => 1, :ms => "120" }
252
+ #
253
+ # This method is used by to_duration_string (to_time_string) behind
254
+ # the scene.
255
+ #
256
+ # Options are :
257
+ #
258
+ # * :months, if set to true, months (M) of 30 days will be taken into
259
+ # account when building up the result
260
+ # * :drop_seconds, if set to true, seconds and milliseconds will be trimmed
261
+ # from the result
262
+ #
263
+ def self.to_duration_hash(seconds, options={})
264
+
265
+ h = {}
266
+
267
+ if seconds.is_a?(Float)
268
+ h[:ms] = (seconds % 1 * 1000).to_i
269
+ seconds = seconds.to_i
270
+ end
271
+
272
+ if options[:drop_seconds]
273
+ h.delete(:ms)
274
+ seconds = (seconds - seconds % 60)
275
+ end
276
+
277
+ durations = options[:months] ? DURATIONS2M : DURATIONS2
278
+
279
+ durations.each do |key, duration|
280
+
281
+ count = seconds / duration
282
+ seconds = seconds % duration
283
+
284
+ h[key.to_sym] = count if count > 0
285
+ end
286
+
287
+ h
288
+ end
289
+
290
+ #--
291
+ # misc
292
+ #++
293
+
294
+ # Produces the UTC string representation of a Time instance
295
+ #
296
+ # like "2009/11/23 11:11:50.947109 UTC"
297
+ #
298
+ def self.utc_to_s(t=Time.now)
299
+
300
+ "#{t.utc.strftime('%Y-%m-%d %H:%M:%S')}.#{sprintf('%06d', t.usec)} UTC"
301
+ end
302
+
303
+ # Produces a hour/min/sec/milli string representation of Time instance
304
+ #
305
+ def self.h_to_s(t=Time.now)
306
+
307
+ "#{t.strftime('%H:%M:%S')}.#{sprintf('%06d', t.usec)}"
308
+ end
309
+
310
+ # Debugging tools...
311
+ #
312
+ class D
313
+
314
+ def self.h_to_s(t=Time.now); Rufus::Scheduler.h_to_s(t); end
315
+ end
316
+ end
317
+ end
318
+
@@ -4,7 +4,7 @@ Gem::Specification.new do |s|
4
4
  s.name = 'rufus-scheduler'
5
5
 
6
6
  s.version = File.read(
7
- File.expand_path('../lib/rufus/sc/version.rb', __FILE__)
7
+ File.expand_path('../lib/rufus/scheduler.rb', __FILE__)
8
8
  ).match(/ VERSION *= *['"]([^'"]+)/)[1]
9
9
 
10
10
  s.platform = Gem::Platform::RUBY
@@ -12,8 +12,8 @@ Gem::Specification.new do |s|
12
12
  s.email = [ 'jmettraux@gmail.com' ]
13
13
  s.homepage = 'http://github.com/jmettraux/rufus-scheduler'
14
14
  s.rubyforge_project = 'rufus'
15
- s.summary = 'job scheduler for Ruby (at, cron, in and every jobs)'
16
15
  s.license = 'MIT'
16
+ s.summary = 'job scheduler for Ruby (at, cron, in and every jobs)'
17
17
 
18
18
  s.description = %{
19
19
  job scheduler for Ruby (at, cron, in and every jobs).
@@ -26,11 +26,37 @@ job scheduler for Ruby (at, cron, in and every jobs).
26
26
  '*.gemspec', '*.txt', '*.rdoc', '*.md'
27
27
  ]
28
28
 
29
- s.add_runtime_dependency 'tzinfo', '>= 0.3.22'
29
+ s.add_runtime_dependency 'tzinfo'
30
30
 
31
31
  s.add_development_dependency 'rake'
32
- s.add_development_dependency 'rspec', '>= 2.7.0'
32
+ s.add_development_dependency 'rspec', '>= 2.13.0'
33
33
 
34
34
  s.require_path = 'lib'
35
+
36
+ s.post_install_message =
37
+ %{
38
+ ***
39
+
40
+ Thanks for installing rufus-scheduler #{s.version}
41
+
42
+ It might not be 100% compatible with rufus-scheduler 2.x.
43
+
44
+ If you encounter issues with this new rufus-scheduler, especially
45
+ if your app worked fine with previous versions of it, you can
46
+
47
+ A) Forget it and peg your Gemfile to rufus-scheduler 2.0.24
48
+
49
+ and / or
50
+
51
+ B) Take some time to carefully report the issue at
52
+ https://github.com/jmettraux/rufus-scheduler/issue
53
+
54
+ For general help about rufus-scheduler, ask via:
55
+ http://stackoverflow.com/questions/ask?tags=rufus-scheduler+ruby
56
+
57
+ Cheers.
58
+
59
+ ***
60
+ }
35
61
  end
36
62
 
@@ -5,13 +5,14 @@
5
5
  # Sat Mar 21 12:55:27 JST 2009
6
6
  #
7
7
 
8
- require 'spec_base'
8
+ require 'tzinfo'
9
+ require 'spec_helper'
9
10
 
10
11
 
11
- describe Rufus::CronLine do
12
+ describe Rufus::Scheduler::CronLine do
12
13
 
13
14
  def cl(cronline_string)
14
- Rufus::CronLine.new(cronline_string)
15
+ Rufus::Scheduler::CronLine.new(cronline_string)
15
16
  end
16
17
 
17
18
  def match(line, time)
@@ -111,7 +112,7 @@ describe Rufus::CronLine do
111
112
  it 'does not support ranges for monthdays (sun#1-sun#2)' do
112
113
 
113
114
  lambda {
114
- Rufus::CronLine.new('* * * * sun#1-sun#2')
115
+ Rufus::Scheduler::CronLine.new('* * * * sun#1-sun#2')
115
116
  }.should raise_error(ArgumentError)
116
117
  end
117
118
 
@@ -165,7 +166,7 @@ describe Rufus::CronLine do
165
166
  describe '#next_time' do
166
167
 
167
168
  def nt(cronline, now)
168
- Rufus::CronLine.new(cronline).next_time(now)
169
+ Rufus::Scheduler::CronLine.new(cronline).next_time(now)
169
170
  end
170
171
 
171
172
  it 'computes the next occurence correctly' do
@@ -185,6 +186,7 @@ describe Rufus::CronLine do
185
186
  # that slowness is gone)
186
187
 
187
188
  nt('0 0 * * thu', now).should == now + 604800
189
+ nt('00 0 * * thu', now).should == now + 604800
188
190
 
189
191
  nt('0 0 * * *', now).should == now + 24 * 3600
190
192
  nt('0 24 * * *', now).should == now + 24 * 3600
@@ -294,12 +296,18 @@ describe Rufus::CronLine do
294
296
  nt('* * L * *', lo(1972, 2, 1)).should == lo(1972, 2, 29)
295
297
  nt('* * L * *', lo(1970, 4, 1)).should == lo(1970, 4, 30)
296
298
  end
299
+
300
+ it 'returns a time with subseconds chopped off' do
301
+
302
+ nt('* * * * *', Time.now).usec.should == 0
303
+ nt('* * * * *', Time.now).iso8601(10).match(/\.0+[^\d]/).should_not == nil
304
+ end
297
305
  end
298
306
 
299
307
  describe '#previous_time' do
300
308
 
301
309
  def pt(cronline, now)
302
- Rufus::CronLine.new(cronline).previous_time(now)
310
+ Rufus::Scheduler::CronLine.new(cronline).previous_time(now)
303
311
  end
304
312
 
305
313
  it 'returns the previous time the cron should have triggered' do
@@ -386,11 +394,11 @@ describe Rufus::CronLine do
386
394
 
387
395
  it 'returns the appropriate "sun#2"-like string' do
388
396
 
389
- class Rufus::CronLine
397
+ class Rufus::Scheduler::CronLine
390
398
  public :monthdays
391
399
  end
392
400
 
393
- cl = Rufus::CronLine.new('* * * * *')
401
+ cl = Rufus::Scheduler::CronLine.new('* * * * *')
394
402
 
395
403
  cl.monthdays(local(1970, 1, 1)).should == %w[ thu#1 thu#-5 ]
396
404
  cl.monthdays(local(1970, 1, 7)).should == %w[ wed#1 wed#-4 ]
@@ -399,5 +407,18 @@ describe Rufus::CronLine do
399
407
  cl.monthdays(local(2011, 3, 11)).should == %w[ fri#2 fri#-3 ]
400
408
  end
401
409
  end
410
+
411
+ describe '#frequency' do
412
+
413
+ it 'returns the shortest delta between two occurrences' do
414
+
415
+ Rufus::Scheduler::CronLine.new('* * * * *').frequency.should == 60
416
+ Rufus::Scheduler::CronLine.new('* * * * * *').frequency.should == 1
417
+
418
+ Rufus::Scheduler::CronLine.new('5 23 * * *').frequency.should == 24 * 3600
419
+ Rufus::Scheduler::CronLine.new('5 * * * *').frequency.should == 3600
420
+ Rufus::Scheduler::CronLine.new('10,20,30 * * * *').frequency.should == 600
421
+ end
422
+ end
402
423
  end
403
424