rufus-scheduler 3.0.8 → 3.2.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.
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2006-2014, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2006-2015, John Mettraux, jmettraux@gmail.com
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  # of this software and associated documentation files (the "Software"), to deal
@@ -22,6 +22,8 @@
22
22
  # Made in Japan.
23
23
  #++
24
24
 
25
+ require 'set'
26
+
25
27
 
26
28
  class Rufus::Scheduler
27
29
 
@@ -40,13 +42,13 @@ class Rufus::Scheduler
40
42
  attr_reader :hours
41
43
  attr_reader :days
42
44
  attr_reader :months
45
+ #attr_reader :monthdays # reader defined below
43
46
  attr_reader :weekdays
44
- attr_reader :monthdays
45
47
  attr_reader :timezone
46
48
 
47
49
  def initialize(line)
48
50
 
49
- raise ArgumentError.new(
51
+ fail ArgumentError.new(
50
52
  "not a string: #{line.inspect}"
51
53
  ) unless line.is_a?(String)
52
54
 
@@ -54,10 +56,9 @@ class Rufus::Scheduler
54
56
 
55
57
  items = line.split
56
58
 
57
- @timezone = (TZInfo::Timezone.get(items.last) rescue nil)
58
- items.pop if @timezone
59
+ @timezone = items.pop if ZoTime.is_timezone?(items.last)
59
60
 
60
- raise ArgumentError.new(
61
+ fail ArgumentError.new(
61
62
  "not a valid cronline : '#{line}'"
62
63
  ) unless items.length == 5 or items.length == 6
63
64
 
@@ -72,7 +73,7 @@ class Rufus::Scheduler
72
73
 
73
74
  [ @seconds, @minutes, @hours, @months ].each do |es|
74
75
 
75
- raise ArgumentError.new(
76
+ fail ArgumentError.new(
76
77
  "invalid cronline: '#{line}'"
77
78
  ) if es && es.find { |e| ! e.is_a?(Fixnum) }
78
79
  end
@@ -82,9 +83,7 @@ class Rufus::Scheduler
82
83
  #
83
84
  def matches?(time)
84
85
 
85
- time = Time.at(time) unless time.kind_of?(Time)
86
-
87
- time = @timezone.utc_to_local(time.getutc) if @timezone
86
+ time = ZoTime.new(time.to_f, @timezone || ENV['TZ']).time
88
87
 
89
88
  return false unless sub_match?(time, :sec, @seconds)
90
89
  return false unless sub_match?(time, :min, @minutes)
@@ -122,37 +121,34 @@ class Rufus::Scheduler
122
121
  #
123
122
  def next_time(from=Time.now)
124
123
 
125
- time = local_time(from)
126
- time = round_to_seconds(time)
127
-
128
- # start at the next second
129
- time = time + 1
124
+ time = nil
125
+ zotime = ZoTime.new(from.to_i + 1, @timezone || ENV['TZ'])
130
126
 
131
127
  loop do
128
+
129
+ time = zotime.time
130
+
132
131
  unless date_match?(time)
133
- dst = time.isdst
134
- time += (24 - time.hour) * 3600 - time.min * 60 - time.sec
135
- time -= 3600 if time.isdst != dst # not necessary for winter, but...
132
+ zotime.add((24 - time.hour) * 3600 - time.min * 60 - time.sec)
136
133
  next
137
134
  end
138
135
  unless sub_match?(time, :hour, @hours)
139
- time += (60 - time.min) * 60 - time.sec; next
136
+ zotime.add((60 - time.min) * 60 - time.sec)
137
+ next
140
138
  end
141
139
  unless sub_match?(time, :min, @minutes)
142
- time += 60 - time.sec; next
140
+ zotime.add(60 - time.sec)
141
+ next
143
142
  end
144
143
  unless sub_match?(time, :sec, @seconds)
145
- time += 1; next
144
+ zotime.add(next_second(time))
145
+ next
146
146
  end
147
147
 
148
148
  break
149
149
  end
150
150
 
151
- global_time(time, from.utc?)
152
-
153
- rescue TZInfo::PeriodNotFound
154
-
155
- next_time(from + 3600)
151
+ time
156
152
  end
157
153
 
158
154
  # Returns the previous time the cronline matched. It's like next_time, but
@@ -160,34 +156,34 @@ class Rufus::Scheduler
160
156
  #
161
157
  def previous_time(from=Time.now)
162
158
 
163
- time = local_time(from)
164
- time = round_to_seconds(time)
165
-
166
- # start at the previous second
167
- time = time - 1
159
+ time = nil
160
+ zotime = ZoTime.new(from.to_i - 1, @timezone || ENV['TZ'])
168
161
 
169
162
  loop do
163
+
164
+ time = zotime.time
165
+
170
166
  unless date_match?(time)
171
- time -= time.hour * 3600 + time.min * 60 + time.sec + 1; next
167
+ zotime.substract(time.hour * 3600 + time.min * 60 + time.sec + 1)
168
+ next
172
169
  end
173
170
  unless sub_match?(time, :hour, @hours)
174
- time -= time.min * 60 + time.sec + 1; next
171
+ zotime.substract(time.min * 60 + time.sec + 1)
172
+ next
175
173
  end
176
174
  unless sub_match?(time, :min, @minutes)
177
- time -= time.sec + 1; next
175
+ zotime.substract(time.sec + 1)
176
+ next
178
177
  end
179
178
  unless sub_match?(time, :sec, @seconds)
180
- time -= 1; next
179
+ zotime.substract(prev_second(time))
180
+ next
181
181
  end
182
182
 
183
183
  break
184
184
  end
185
185
 
186
- global_time(time, from.utc?)
187
-
188
- rescue TZInfo::PeriodNotFound
189
-
190
- previous_time(time)
186
+ time
191
187
  end
192
188
 
193
189
  # Returns an array of 6 arrays (seconds, minutes, hours, days,
@@ -197,14 +193,14 @@ class Rufus::Scheduler
197
193
  def to_array
198
194
 
199
195
  [
200
- @seconds,
201
- @minutes,
202
- @hours,
203
- @days,
204
- @months,
205
- @weekdays,
206
- @monthdays,
207
- @timezone ? @timezone.name : nil
196
+ toa(@seconds),
197
+ toa(@minutes),
198
+ toa(@hours),
199
+ toa(@days),
200
+ toa(@months),
201
+ toa(@weekdays),
202
+ toa(@monthdays),
203
+ @timezone
208
204
  ]
209
205
  end
210
206
 
@@ -212,24 +208,25 @@ class Rufus::Scheduler
212
208
  # cron line.
213
209
  #
214
210
  # #brute_frequency, on the other hand, will compute the frequency by
215
- # examining a whole, that can take more than seconds for a seconds
211
+ # examining a whole year, that can take more than seconds for a seconds
216
212
  # level cron...
217
213
  #
218
214
  def frequency
219
215
 
220
216
  return brute_frequency unless @seconds && @seconds.length > 1
221
217
 
222
- delta = 60
223
- prev = @seconds[0]
218
+ secs = toa(@seconds)
224
219
 
225
- @seconds[1..-1].each do |sec|
220
+ secs[1..-1].inject([ secs[0], 60 ]) { |(prev, delta), sec|
226
221
  d = sec - prev
227
- delta = d if d < delta
228
- end
229
-
230
- delta
222
+ [ sec, d < delta ? d : delta ]
223
+ }[1]
231
224
  end
232
225
 
226
+ # Caching facility. Currently only used for brute frequencies.
227
+ #
228
+ @cache = {}; class << self; attr_reader :cache; end
229
+
233
230
  # Returns the shortest delta between two potential occurences of the
234
231
  # schedule described by this cronline.
235
232
  #
@@ -246,16 +243,13 @@ class Rufus::Scheduler
246
243
  # Of course, this method can get VERY slow if you call on it a second-
247
244
  # based cronline...
248
245
  #
249
- # Since it's a rarely used method, I haven't taken the time to make it
250
- # smarter/faster.
251
- #
252
- # One obvious improvement would be to cache the result once computed...
253
- #
254
- # See https://github.com/jmettraux/rufus-scheduler/issues/89
255
- # for a discussion about this method.
256
- #
257
246
  def brute_frequency
258
247
 
248
+ key = "brute_frequency:#{@original}"
249
+
250
+ delta = self.class.cache[key]
251
+ return delta if delta
252
+
259
253
  delta = 366 * DAY_S
260
254
 
261
255
  t0 = previous_time(Time.local(2000, 1, 1))
@@ -270,16 +264,53 @@ class Rufus::Scheduler
270
264
  delta = d if d < delta
271
265
 
272
266
  break if @months == nil && t1.month == 2
273
- break if t1.year == 2001
267
+ break if t1.year >= 2001
274
268
 
275
269
  t0 = t1
276
270
  end
277
271
 
278
- delta
272
+ self.class.cache[key] = delta
273
+ end
274
+
275
+ def next_second(time)
276
+
277
+ secs = toa(@seconds)
278
+
279
+ return secs.first + 60 - time.sec if time.sec > secs.last
280
+
281
+ secs.shift while secs.first < time.sec
282
+
283
+ secs.first - time.sec
284
+ end
285
+
286
+ def prev_second(time)
287
+
288
+ secs = toa(@seconds)
289
+
290
+ return time.sec + 60 - secs.last if time.sec < secs.first
291
+
292
+ secs.pop while time.sec < secs.last
293
+
294
+ time.sec - secs.last
279
295
  end
280
296
 
281
297
  protected
282
298
 
299
+ def sc_sort(a)
300
+
301
+ a.sort_by { |e| e.is_a?(String) ? 61 : e.to_i }
302
+ end
303
+
304
+ if RUBY_VERSION >= '1.9'
305
+ def toa(item)
306
+ item == nil ? nil : item.to_a
307
+ end
308
+ else
309
+ def toa(item)
310
+ item.is_a?(Set) ? sc_sort(item.to_a) : item
311
+ end
312
+ end
313
+
283
314
  WEEKDAYS = %w[ sun mon tue wed thu fri sat ]
284
315
  DAY_S = 24 * 3600
285
316
  WEEK_S = 7 * DAY_S
@@ -297,7 +328,7 @@ class Rufus::Scheduler
297
328
 
298
329
  if m = it.match(/^(.+)#(l|-?[12345])$/)
299
330
 
300
- raise ArgumentError.new(
331
+ fail ArgumentError.new(
301
332
  "ranges are not supported for monthdays (#{it})"
302
333
  ) if m[1].index('-')
303
334
 
@@ -310,7 +341,7 @@ class Rufus::Scheduler
310
341
  expr = it.dup
311
342
  WEEKDAYS.each_with_index { |a, i| expr.gsub!(/#{a}/, i.to_s) }
312
343
 
313
- raise ArgumentError.new(
344
+ fail ArgumentError.new(
314
345
  "invalid weekday expression (#{it})"
315
346
  ) if expr !~ /^0*[0-7](-0*[0-7])?$/
316
347
 
@@ -321,7 +352,7 @@ class Rufus::Scheduler
321
352
  end
322
353
  end
323
354
 
324
- weekdays = weekdays.uniq if weekdays
355
+ weekdays = weekdays.uniq.sort if weekdays
325
356
 
326
357
  [ weekdays, monthdays ]
327
358
  end
@@ -332,11 +363,13 @@ class Rufus::Scheduler
332
363
 
333
364
  r = item.split(',').map { |i| parse_range(i.strip, min, max) }.flatten
334
365
 
335
- raise ArgumentError.new(
366
+ fail ArgumentError.new(
336
367
  "found duplicates in #{item.inspect}"
337
368
  ) if r.uniq.size < r.size
338
369
 
339
- r
370
+ r = sc_sort(r)
371
+
372
+ Set.new(r)
340
373
  end
341
374
 
342
375
  RANGE_REGEX = /^(\*|\d{1,2})(?:-(\d{1,2}))?(?:\/(\d{1,2}))?$/
@@ -345,11 +378,11 @@ class Rufus::Scheduler
345
378
 
346
379
  return %w[ L ] if item == 'L'
347
380
 
348
- item = '*' + item if item.match(/^\//)
381
+ item = '*' + item if item[0, 1] == '/'
349
382
 
350
383
  m = item.match(RANGE_REGEX)
351
384
 
352
- raise ArgumentError.new(
385
+ fail ArgumentError.new(
353
386
  "cannot parse #{item.inspect}"
354
387
  ) unless m
355
388
 
@@ -363,7 +396,7 @@ class Rufus::Scheduler
363
396
  inc = m[3]
364
397
  inc = inc ? inc.to_i : 1
365
398
 
366
- raise ArgumentError.new(
399
+ fail ArgumentError.new(
367
400
  "#{item.inspect} is not in range #{min}..#{max}"
368
401
  ) if sta < min || edn > max
369
402
 
@@ -435,32 +468,6 @@ class Rufus::Scheduler
435
468
 
436
469
  [ "#{WEEKDAYS[date.wday]}##{pos}", "#{WEEKDAYS[date.wday]}##{neg}" ]
437
470
  end
438
-
439
- def local_time(time)
440
-
441
- @timezone ? @timezone.utc_to_local(time.getutc) : time
442
- end
443
-
444
- def global_time(time, from_in_utc)
445
-
446
- if @timezone
447
- time =
448
- begin
449
- @timezone.local_to_utc(time)
450
- rescue TZInfo::AmbiguousTime
451
- @timezone.local_to_utc(time, time.isdst)
452
- end
453
- time = time.getlocal unless from_in_utc
454
- end
455
-
456
- time
457
- end
458
-
459
- def round_to_seconds(time)
460
-
461
- # Ruby 1.8 doesn't have #round
462
- time.respond_to?(:round) ? time.round : time - time.usec * 1e-6
463
- end
464
471
  end
465
472
  end
466
473
 
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2006-2014, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2006-2015, John Mettraux, jmettraux@gmail.com
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  # of this software and associated documentation files (the "Software"), to deal
@@ -22,6 +22,7 @@
22
22
  # Made in Japan.
23
23
  #++
24
24
 
25
+
25
26
  module Rufus
26
27
 
27
28
  class Scheduler
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2006-2014, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2006-2015, John Mettraux, jmettraux@gmail.com
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  # of this software and associated documentation files (the "Software"), to deal
@@ -91,7 +91,7 @@ module Rufus
91
91
 
92
92
  @id = determine_id
93
93
 
94
- raise(
94
+ fail(
95
95
  ArgumentError,
96
96
  'missing block or callable to schedule',
97
97
  caller[2..-1]
@@ -117,15 +117,16 @@ module Rufus
117
117
 
118
118
  def trigger(time)
119
119
 
120
- set_next_time(false, time)
120
+ set_next_time(time)
121
121
 
122
- return if opts[:overlap] == false && running?
123
-
124
- r =
122
+ return if (
123
+ opts[:overlap] == false &&
124
+ running?
125
+ )
126
+ return if (
125
127
  callback(:confirm_lock, time) &&
126
128
  callback(:on_pre_trigger, time)
127
-
128
- return if r == false
129
+ ) == false
129
130
 
130
131
  @count += 1
131
132
 
@@ -234,7 +235,7 @@ module Rufus
234
235
 
235
236
  rescue StandardError => se
236
237
 
237
- raise se unless do_rescue
238
+ fail se unless do_rescue
238
239
 
239
240
  return if se.is_a?(KillSignal) # discard
240
241
 
@@ -272,7 +273,7 @@ module Rufus
272
273
 
273
274
  def post_trigger(time)
274
275
 
275
- set_next_time(true, time)
276
+ set_next_time(time, true)
276
277
 
277
278
  callback(:on_post_trigger, time)
278
279
  end
@@ -283,7 +284,7 @@ module Rufus
283
284
  Thread.new do
284
285
 
285
286
  Thread.current[@scheduler.thread_key] = true
286
- Thread.current[:rufus_scheduler_job_thread] = true
287
+ Thread.current[:rufus_scheduler_work_thread] = true
287
288
 
288
289
  loop do
289
290
 
@@ -339,7 +340,7 @@ module Rufus
339
340
 
340
341
  def occurrences(time0, time1)
341
342
 
342
- time >= time0 && time <= time1 ? [ time ] : []
343
+ (time >= time0 && time <= time1) ? [ time ] : []
343
344
  end
344
345
 
345
346
  protected
@@ -350,13 +351,13 @@ module Rufus
350
351
  self.class.name.split(':').last.downcase[0..-4],
351
352
  @scheduled_at.to_f,
352
353
  @next_time.to_f,
353
- opts.hash.abs
354
+ self.hash.abs
354
355
  ].map(&:to_s).join('_')
355
356
  end
356
357
 
357
358
  # There is no next_time for one time jobs, hence the false.
358
359
  #
359
- def set_next_time(is_post, trigger_time)
360
+ def set_next_time(trigger_time, is_post=false)
360
361
 
361
362
  @next_time = is_post ? nil : false
362
363
  end
@@ -390,7 +391,7 @@ module Rufus
390
391
  attr_reader :paused_at
391
392
 
392
393
  attr_reader :first_at
393
- attr_accessor :last_at
394
+ attr_reader :last_at
394
395
  attr_accessor :times
395
396
 
396
397
  def initialize(scheduler, duration, opts, block)
@@ -401,47 +402,53 @@ module Rufus
401
402
 
402
403
  @times = opts[:times]
403
404
 
404
- raise ArgumentError.new(
405
+ fail ArgumentError.new(
405
406
  "cannot accept :times => #{@times.inspect}, not nil or an int"
406
407
  ) unless @times == nil || @times.is_a?(Fixnum)
407
408
 
408
409
  self.first_at =
409
410
  opts[:first] || opts[:first_time] ||
410
411
  opts[:first_at] || opts[:first_in] ||
411
- 0
412
+ nil
412
413
  self.last_at =
413
414
  opts[:last] || opts[:last_at] || opts[:last_in]
414
415
  end
415
416
 
416
417
  def first_at=(first)
417
418
 
418
- n = Time.now
419
- first = n + 0.001 if first == :now || first == :immediately
419
+ return (@first_at = nil) if first == nil
420
+
421
+ n0 = Time.now
422
+ n1 = n0 + 0.003
423
+
424
+ first = n0 if first == :now || first == :immediately || first == 0
420
425
 
421
426
  @first_at = Rufus::Scheduler.parse_to_time(first)
427
+ @first_at = n1 if @first_at >= n0 && @first_at < n1
422
428
 
423
- raise ArgumentError.new(
429
+ fail ArgumentError.new(
424
430
  "cannot set first[_at|_in] in the past: " +
425
431
  "#{first.inspect} -> #{@first_at.inspect}"
426
- ) if first != 0 && @first_at < n
432
+ ) if @first_at < n0
433
+
434
+ @first_at
427
435
  end
428
436
 
429
437
  def last_at=(last)
430
438
 
431
439
  @last_at = last ? Rufus::Scheduler.parse_to_time(last) : nil
432
440
 
433
- raise ArgumentError.new(
441
+ fail ArgumentError.new(
434
442
  "cannot set last[_at|_in] in the past: " +
435
443
  "#{last.inspect} -> #{@last_at.inspect}"
436
444
  ) if last && @last_at < Time.now
445
+
446
+ @last_at
437
447
  end
438
448
 
439
449
  def trigger(time)
440
450
 
441
451
  return if @paused_at
442
- return if time < @first_at
443
- #
444
- # TODO: remove me when @first_at gets reworked
445
452
 
446
453
  return (@next_time = nil) if @times && @times < 1
447
454
  return (@next_time = nil) if @last_at && time >= @last_at
@@ -473,7 +480,7 @@ module Rufus
473
480
  [
474
481
  self.class.name.split(':').last.downcase[0..-4],
475
482
  @scheduled_at.to_f,
476
- opts.hash.abs
483
+ self.hash.abs
477
484
  ].map(&:to_s).join('_')
478
485
  end
479
486
 
@@ -522,25 +529,26 @@ module Rufus
522
529
 
523
530
  @frequency = Rufus::Scheduler.parse_in(@original)
524
531
 
525
- raise ArgumentError.new(
532
+ fail ArgumentError.new(
526
533
  "cannot schedule #{self.class} with a frequency " +
527
534
  "of #{@frequency.inspect} (#{@original.inspect})"
528
535
  ) if @frequency <= 0
529
536
 
530
- set_next_time(false, nil)
537
+ set_next_time(nil)
531
538
  end
532
539
 
533
540
  protected
534
541
 
535
- def set_next_time(is_post, trigger_time)
542
+ def set_next_time(trigger_time, is_post=false)
536
543
 
537
544
  return if is_post
538
545
 
546
+ n = Time.now
547
+
539
548
  @next_time =
540
- if trigger_time
541
- trigger_time + @frequency
542
- elsif @first_at < Time.now
543
- Time.now + @frequency
549
+ if @first_at == nil || @first_at < n
550
+ nt = (@next_time || trigger_time || n) + @frequency
551
+ nt > n ? nt : (trigger_time || n) + @frequency
544
552
  else
545
553
  @first_at
546
554
  end
@@ -562,23 +570,23 @@ module Rufus
562
570
 
563
571
  @interval = Rufus::Scheduler.parse_in(@original)
564
572
 
565
- raise ArgumentError.new(
573
+ fail ArgumentError.new(
566
574
  "cannot schedule #{self.class} with an interval " +
567
575
  "of #{@interval.inspect} (#{@original.inspect})"
568
576
  ) if @interval <= 0
569
577
 
570
- set_next_time(false, nil)
578
+ set_next_time(nil)
571
579
  end
572
580
 
573
581
  protected
574
582
 
575
- def set_next_time(is_post, trigger_time)
583
+ def set_next_time(trigger_time, is_post=false)
576
584
 
577
585
  @next_time =
578
586
  if is_post
579
587
  Time.now + @interval
580
588
  elsif trigger_time.nil?
581
- if @first_at < Time.now
589
+ if @first_at == nil || @first_at < Time.now
582
590
  Time.now + @interval
583
591
  else
584
592
  @first_at
@@ -601,7 +609,7 @@ module Rufus
601
609
  super(scheduler, cronline, opts, block)
602
610
 
603
611
  @cron_line = opts[:_t] || CronLine.new(cronline)
604
- @next_time = @cron_line.next_time
612
+ set_next_time(nil)
605
613
  end
606
614
 
607
615
  def frequency
@@ -616,14 +624,18 @@ module Rufus
616
624
 
617
625
  protected
618
626
 
619
- def set_next_time(is_post, trigger_time)
627
+ def set_next_time(trigger_time, is_post=false)
620
628
 
621
- @next_time = @cron_line.next_time
629
+ @next_time = next_time_from(trigger_time || Time.now)
622
630
  end
623
631
 
624
632
  def next_time_from(time)
625
633
 
626
- @cron_line.next_time(time)
634
+ if @first_at == nil || @first_at <= time
635
+ @cron_line.next_time(time)
636
+ else
637
+ @first_at
638
+ end
627
639
  end
628
640
  end
629
641
  end