rufus-scheduler 3.2.0 → 3.3.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-2015, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2006-2016, 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
@@ -36,6 +36,7 @@ class Rufus::Scheduler
36
36
  # The string used for creating this cronline instance.
37
37
  #
38
38
  attr_reader :original
39
+ attr_reader :original_timezone
39
40
 
40
41
  attr_reader :seconds
41
42
  attr_reader :minutes
@@ -53,10 +54,15 @@ class Rufus::Scheduler
53
54
  ) unless line.is_a?(String)
54
55
 
55
56
  @original = line
57
+ @original_timezone = nil
56
58
 
57
59
  items = line.split
58
60
 
59
- @timezone = items.pop if ZoTime.is_timezone?(items.last)
61
+ if @timezone = ZoTime.get_tzone(items.last)
62
+ @original_timezone = items.pop
63
+ else
64
+ @timezone = ZoTime.get_tzone(:current)
65
+ end
60
66
 
61
67
  fail ArgumentError.new(
62
68
  "not a valid cronline : '#{line}'"
@@ -67,7 +73,7 @@ class Rufus::Scheduler
67
73
  @seconds = offset == 1 ? parse_item(items[0], 0, 59) : [ 0 ]
68
74
  @minutes = parse_item(items[0 + offset], 0, 59)
69
75
  @hours = parse_item(items[1 + offset], 0, 24)
70
- @days = parse_item(items[2 + offset], 1, 31)
76
+ @days = parse_item(items[2 + offset], -30, 31)
71
77
  @months = parse_item(items[3 + offset], 1, 12)
72
78
  @weekdays, @monthdays = parse_weekdays(items[4 + offset])
73
79
 
@@ -77,18 +83,26 @@ class Rufus::Scheduler
77
83
  "invalid cronline: '#{line}'"
78
84
  ) if es && es.find { |e| ! e.is_a?(Fixnum) }
79
85
  end
86
+
87
+ if @days && @days.include?(0) # gh-221
88
+
89
+ fail ArgumentError.new('invalid day 0 in cronline')
90
+ end
80
91
  end
81
92
 
82
93
  # Returns true if the given time matches this cron line.
83
94
  #
84
95
  def matches?(time)
85
96
 
86
- time = ZoTime.new(time.to_f, @timezone || ENV['TZ']).time
97
+ # FIXME Don't create a new ZoTime if time is already a ZoTime in same
98
+ # zone ...
99
+ # Wait, this seems only used in specs...
100
+ t = ZoTime.new(time.to_f, @timezone)
87
101
 
88
- return false unless sub_match?(time, :sec, @seconds)
89
- return false unless sub_match?(time, :min, @minutes)
90
- return false unless sub_match?(time, :hour, @hours)
91
- return false unless date_match?(time)
102
+ return false unless sub_match?(t, :sec, @seconds)
103
+ return false unless sub_match?(t, :min, @minutes)
104
+ return false unless sub_match?(t, :hour, @hours)
105
+ return false unless date_match?(t)
92
106
  true
93
107
  end
94
108
 
@@ -119,36 +133,36 @@ class Rufus::Scheduler
119
133
  #
120
134
  # (Thanks to K Liu for the note and the examples)
121
135
  #
122
- def next_time(from=Time.now)
136
+ def next_time(from=ZoTime.now)
123
137
 
124
- time = nil
125
- zotime = ZoTime.new(from.to_i + 1, @timezone || ENV['TZ'])
138
+ nt = nil
139
+ zt = ZoTime.new(from.to_i + 1, @timezone)
126
140
 
127
141
  loop do
128
142
 
129
- time = zotime.time
143
+ nt = zt.dup
130
144
 
131
- unless date_match?(time)
132
- zotime.add((24 - time.hour) * 3600 - time.min * 60 - time.sec)
145
+ unless date_match?(nt)
146
+ zt.add((24 - nt.hour) * 3600 - nt.min * 60 - nt.sec)
133
147
  next
134
148
  end
135
- unless sub_match?(time, :hour, @hours)
136
- zotime.add((60 - time.min) * 60 - time.sec)
149
+ unless sub_match?(nt, :hour, @hours)
150
+ zt.add((60 - nt.min) * 60 - nt.sec)
137
151
  next
138
152
  end
139
- unless sub_match?(time, :min, @minutes)
140
- zotime.add(60 - time.sec)
153
+ unless sub_match?(nt, :min, @minutes)
154
+ zt.add(60 - nt.sec)
141
155
  next
142
156
  end
143
- unless sub_match?(time, :sec, @seconds)
144
- zotime.add(next_second(time))
157
+ unless sub_match?(nt, :sec, @seconds)
158
+ zt.add(next_second(nt))
145
159
  next
146
160
  end
147
161
 
148
162
  break
149
163
  end
150
164
 
151
- time
165
+ nt
152
166
  end
153
167
 
154
168
  # Returns the previous time the cronline matched. It's like next_time, but
@@ -156,41 +170,41 @@ class Rufus::Scheduler
156
170
  #
157
171
  def previous_time(from=Time.now)
158
172
 
159
- time = nil
160
- zotime = ZoTime.new(from.to_i - 1, @timezone || ENV['TZ'])
173
+ pt = nil
174
+ zt = ZoTime.new(from.to_i - 1, @timezone)
161
175
 
162
176
  loop do
163
177
 
164
- time = zotime.time
178
+ pt = zt.dup
165
179
 
166
- unless date_match?(time)
167
- zotime.substract(time.hour * 3600 + time.min * 60 + time.sec + 1)
180
+ unless date_match?(pt)
181
+ zt.substract(pt.hour * 3600 + pt.min * 60 + pt.sec + 1)
168
182
  next
169
183
  end
170
- unless sub_match?(time, :hour, @hours)
171
- zotime.substract(time.min * 60 + time.sec + 1)
184
+ unless sub_match?(pt, :hour, @hours)
185
+ zt.substract(pt.min * 60 + pt.sec + 1)
172
186
  next
173
187
  end
174
- unless sub_match?(time, :min, @minutes)
175
- zotime.substract(time.sec + 1)
188
+ unless sub_match?(pt, :min, @minutes)
189
+ zt.substract(pt.sec + 1)
176
190
  next
177
191
  end
178
- unless sub_match?(time, :sec, @seconds)
179
- zotime.substract(prev_second(time))
192
+ unless sub_match?(pt, :sec, @seconds)
193
+ zt.substract(prev_second(pt))
180
194
  next
181
195
  end
182
196
 
183
197
  break
184
198
  end
185
199
 
186
- time
200
+ pt
187
201
  end
188
202
 
189
203
  # Returns an array of 6 arrays (seconds, minutes, hours, days,
190
204
  # months, weekdays).
191
- # This method is used by the cronline unit tests.
205
+ # This method is mostly used by the cronline specs.
192
206
  #
193
- def to_array
207
+ def to_a
194
208
 
195
209
  [
196
210
  toa(@seconds),
@@ -200,9 +214,10 @@ class Rufus::Scheduler
200
214
  toa(@months),
201
215
  toa(@weekdays),
202
216
  toa(@monthdays),
203
- @timezone
217
+ @timezone.name
204
218
  ]
205
219
  end
220
+ alias to_array to_a
206
221
 
207
222
  # Returns a quickly computed approximation of the frequency for this
208
223
  # cron line.
@@ -259,7 +274,9 @@ class Rufus::Scheduler
259
274
  break if delta <= 1
260
275
  break if delta <= 60 && @seconds && @seconds.size == 1
261
276
 
277
+ #st = Time.now
262
278
  t1 = next_time(t0)
279
+ #p Time.now - st
263
280
  d = t1 - t0
264
281
  delta = d if d < delta
265
282
 
@@ -313,39 +330,38 @@ class Rufus::Scheduler
313
330
 
314
331
  WEEKDAYS = %w[ sun mon tue wed thu fri sat ]
315
332
  DAY_S = 24 * 3600
316
- WEEK_S = 7 * DAY_S
317
333
 
318
334
  def parse_weekdays(item)
319
335
 
320
336
  return nil if item == '*'
321
337
 
322
- items = item.downcase.split(',')
323
-
324
338
  weekdays = nil
325
339
  monthdays = nil
326
340
 
327
- items.each do |it|
341
+ item.downcase.split(',').each do |it|
328
342
 
329
- if m = it.match(/^(.+)#(l|-?[12345])$/)
343
+ WEEKDAYS.each_with_index { |a, i| it.gsub!(/#{a}/, i.to_s) }
344
+
345
+ it = it.gsub(/([^#])l/, '\1#-1')
346
+ # "5L" == "5#-1" == the last Friday
347
+
348
+ if m = it.match(/\A(.+)#(l|-?[12345])\z/)
330
349
 
331
350
  fail ArgumentError.new(
332
351
  "ranges are not supported for monthdays (#{it})"
333
352
  ) if m[1].index('-')
334
353
 
335
- expr = it.gsub(/#l/, '#-1')
354
+ it = it.gsub(/#l/, '#-1')
336
355
 
337
- (monthdays ||= []) << expr
356
+ (monthdays ||= []) << it
338
357
 
339
358
  else
340
359
 
341
- expr = it.dup
342
- WEEKDAYS.each_with_index { |a, i| expr.gsub!(/#{a}/, i.to_s) }
343
-
344
360
  fail ArgumentError.new(
345
- "invalid weekday expression (#{it})"
346
- ) if expr !~ /^0*[0-7](-0*[0-7])?$/
361
+ "invalid weekday expression (#{item})"
362
+ ) if it !~ /\A0*[0-7](-0*[0-7])?\z/
347
363
 
348
- its = expr.index('-') ? parse_range(expr, 0, 7) : [ Integer(expr) ]
364
+ its = it.index('-') ? parse_range(it, 0, 7) : [ Integer(it) ]
349
365
  its = its.collect { |i| i == 7 ? 0 : i }
350
366
 
351
367
  (weekdays ||= []).concat(its)
@@ -372,7 +388,7 @@ class Rufus::Scheduler
372
388
  Set.new(r)
373
389
  end
374
390
 
375
- RANGE_REGEX = /^(\*|\d{1,2})(?:-(\d{1,2}))?(?:\/(\d{1,2}))?$/
391
+ RANGE_REGEX = /\A(\*|-?\d{1,2})(?:-(-?\d{1,2}))?(?:\/(\d{1,2}))?\z/
376
392
 
377
393
  def parse_range(item, min, max)
378
394
 
@@ -386,8 +402,10 @@ class Rufus::Scheduler
386
402
  "cannot parse #{item.inspect}"
387
403
  ) unless m
388
404
 
405
+ mmin = min == -30 ? 1 : min # days
406
+
389
407
  sta = m[1]
390
- sta = sta == '*' ? min : sta.to_i
408
+ sta = sta == '*' ? mmin : sta.to_i
391
409
 
392
410
  edn = m[2]
393
411
  edn = edn ? edn.to_i : sta
@@ -396,16 +414,28 @@ class Rufus::Scheduler
396
414
  inc = m[3]
397
415
  inc = inc ? inc.to_i : 1
398
416
 
417
+ fail ArgumentError.new(
418
+ "#{item.inspect} positive/negative ranges not allowed"
419
+ ) if (sta < 0 && edn > 0) || (sta > 0 && edn < 0)
420
+
421
+ fail ArgumentError.new(
422
+ "#{item.inspect} descending day ranges not allowed"
423
+ ) if min == -30 && sta > edn
424
+
399
425
  fail ArgumentError.new(
400
426
  "#{item.inspect} is not in range #{min}..#{max}"
401
427
  ) if sta < min || edn > max
402
428
 
429
+ fail ArgumentError.new(
430
+ "#{item.inspect} increment must be greater than zero"
431
+ ) if inc == 0
432
+
403
433
  r = []
404
434
  val = sta
405
435
 
406
436
  loop do
407
437
  v = val
408
- v = 0 if max == 24 && v == 24
438
+ v = 0 if max == 24 && v == 24 # hours
409
439
  r << v
410
440
  break if inc == 1 && val == edn
411
441
  val += inc
@@ -416,57 +446,52 @@ class Rufus::Scheduler
416
446
  r.uniq
417
447
  end
418
448
 
449
+ # FIXME: Eventually split into day_match?, hour_match? and monthdays_match?o
450
+ #
419
451
  def sub_match?(time, accessor, values)
420
452
 
421
- value = time.send(accessor)
422
-
423
453
  return true if values.nil?
424
- return true if values.include?('L') && (time + DAY_S).day == 1
425
454
 
426
- return true if value == 0 && accessor == :hour && values.include?(24)
455
+ value = time.send(accessor)
427
456
 
428
- values.include?(value)
429
- end
457
+ if accessor == :day
430
458
 
431
- def monthday_match?(date, values)
459
+ values.each do |v|
460
+ return true if v == 'L' && (time + DAY_S).day == 1
461
+ return true if v.to_i < 0 && (time + (1 - v) * DAY_S).day == 1
462
+ end
463
+ end
432
464
 
433
- return true if values.nil?
465
+ if accessor == :hour
434
466
 
435
- today_values = monthdays(date)
467
+ return true if value == 0 && values.include?(24)
468
+ end
436
469
 
437
- (today_values & values).any?
438
- end
470
+ if accessor == :monthdays
439
471
 
440
- def date_match?(date)
472
+ return true if (values & value).any?
473
+ end
441
474
 
442
- return false unless sub_match?(date, :day, @days)
443
- return false unless sub_match?(date, :month, @months)
444
- return false unless sub_match?(date, :wday, @weekdays)
445
- return false unless monthday_match?(date, @monthdays)
446
- true
475
+ values.include?(value)
447
476
  end
448
477
 
449
- def monthdays(date)
450
-
451
- pos = 1
452
- d = date.dup
453
-
454
- loop do
455
- d = d - WEEK_S
456
- break if d.month != date.month
457
- pos = pos + 1
458
- end
459
-
460
- neg = -1
461
- d = date.dup
478
+ # def monthday_match?(zt, values)
479
+ #
480
+ # return true if values.nil?
481
+ #
482
+ # today_values = monthdays(zt)
483
+ #
484
+ # (today_values & values).any?
485
+ # end
462
486
 
463
- loop do
464
- d = d + WEEK_S
465
- break if d.month != date.month
466
- neg = neg - 1
467
- end
487
+ def date_match?(zt)
468
488
 
469
- [ "#{WEEKDAYS[date.wday]}##{pos}", "#{WEEKDAYS[date.wday]}##{neg}" ]
489
+ return false unless sub_match?(zt, :day, @days)
490
+ return false unless sub_match?(zt, :month, @months)
491
+ return false unless sub_match?(zt, :wday, @weekdays)
492
+ #return false unless monthday_match?(zt, @monthdays)
493
+ return false unless sub_match?(zt, :monthdays, @monthdays)
494
+ true
470
495
  end
471
496
  end
472
497
  end
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2006-2015, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2006-2016, 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
@@ -53,10 +53,14 @@ module Rufus
53
53
 
54
54
  def each(now, &block)
55
55
 
56
- to_a.sort_by { |j| j.next_time || (now + 1) }.each do |job|
56
+ to_a.sort_by do |job|
57
57
 
58
- break unless job.next_time
59
- break if job.next_time > now
58
+ job.next_time || (now + 1)
59
+
60
+ end.each do |job|
61
+
62
+ nt = job.next_time
63
+ break if ( ! nt) || (nt > now)
60
64
 
61
65
  block.call(job)
62
66
  end
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2006-2015, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2006-2016, 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
@@ -53,6 +53,10 @@ module Rufus
53
53
  #
54
54
  attr_accessor :next_time
55
55
 
56
+ # previous "next trigger time"
57
+ #
58
+ attr_accessor :previous_time
59
+
56
60
  # anything with a #call(job[, timet]) method,
57
61
  # what gets actually triggered
58
62
  #
@@ -82,7 +86,7 @@ module Rufus
82
86
  nil
83
87
  end
84
88
 
85
- @scheduled_at = Time.now
89
+ @scheduled_at = Rufus::Scheduler::ZoTime.now
86
90
  @unscheduled_at = nil
87
91
  @last_time = nil
88
92
 
@@ -117,29 +121,25 @@ module Rufus
117
121
 
118
122
  def trigger(time)
119
123
 
124
+ @previous_time = @next_time
120
125
  set_next_time(time)
121
126
 
122
- return if (
123
- opts[:overlap] == false &&
124
- running?
125
- )
126
- return if (
127
- callback(:confirm_lock, time) &&
128
- callback(:on_pre_trigger, time)
129
- ) == false
127
+ do_trigger(time)
128
+ end
130
129
 
131
- @count += 1
130
+ # Trigger the job right now, off of its schedule.
131
+ #
132
+ # Done in collaboration with Piavka in
133
+ # https://github.com/jmettraux/rufus-scheduler/issues/214
134
+ #
135
+ def trigger_off_schedule(time=Rufus::Scheduler::ZoTime.now)
132
136
 
133
- if opts[:blocking]
134
- do_trigger(time)
135
- else
136
- do_trigger_in_thread(time)
137
- end
137
+ do_trigger(time)
138
138
  end
139
139
 
140
140
  def unschedule
141
141
 
142
- @unscheduled_at = Time.now
142
+ @unscheduled_at = Rufus::Scheduler::ZoTime.now
143
143
  end
144
144
 
145
145
  def threads
@@ -199,7 +199,7 @@ module Rufus
199
199
  #
200
200
  def call(do_rescue=false)
201
201
 
202
- do_call(Time.now, do_rescue)
202
+ do_call(Rufus::Scheduler::ZoTime.now, do_rescue)
203
203
  end
204
204
 
205
205
  protected
@@ -246,7 +246,27 @@ module Rufus
246
246
 
247
247
  def do_trigger(time)
248
248
 
249
- t = Time.now
249
+ return if (
250
+ opts[:overlap] == false &&
251
+ running?
252
+ )
253
+ return if (
254
+ callback(:confirm_lock, time) &&
255
+ callback(:on_pre_trigger, time)
256
+ ) == false
257
+
258
+ @count += 1
259
+
260
+ if opts[:blocking]
261
+ trigger_now(time)
262
+ else
263
+ trigger_queue(time)
264
+ end
265
+ end
266
+
267
+ def trigger_now(time)
268
+
269
+ t = Rufus::Scheduler::ZoTime.now
250
270
  # if there are mutexes, t might be really bigger than time
251
271
 
252
272
  Thread.current[:rufus_scheduler_job] = self
@@ -260,7 +280,7 @@ module Rufus
260
280
  ensure
261
281
 
262
282
  @last_work_time =
263
- Time.now - Thread.current[:rufus_scheduler_time]
283
+ Rufus::Scheduler::ZoTime.now - Thread.current[:rufus_scheduler_time]
264
284
  @mean_work_time =
265
285
  ((@count - 1) * @mean_work_time + @last_work_time) / @count
266
286
 
@@ -297,7 +317,7 @@ module Rufus
297
317
  begin
298
318
 
299
319
  (job.opts[:mutex] || []).reduce(
300
- lambda { job.do_trigger(time) }
320
+ lambda { job.trigger_now(time) }
301
321
  ) do |b, m|
302
322
  lambda { mutex(m).synchronize { b.call } }
303
323
  end.call
@@ -316,9 +336,11 @@ module Rufus
316
336
  # but since it has to be done as quickly as possible.
317
337
  # So, whoever is running first (scheduler thread vs job thread)
318
338
  # sets this information
339
+
340
+ thread
319
341
  end
320
342
 
321
- def do_trigger_in_thread(time)
343
+ def trigger_queue(time)
322
344
 
323
345
  threads = @scheduler.work_threads
324
346
 
@@ -418,12 +440,12 @@ module Rufus
418
440
 
419
441
  return (@first_at = nil) if first == nil
420
442
 
421
- n0 = Time.now
443
+ n0 = Rufus::Scheduler::ZoTime.now
422
444
  n1 = n0 + 0.003
423
445
 
424
446
  first = n0 if first == :now || first == :immediately || first == 0
425
447
 
426
- @first_at = Rufus::Scheduler.parse_to_time(first)
448
+ @first_at = ZoTime.make(first)
427
449
  @first_at = n1 if @first_at >= n0 && @first_at < n1
428
450
 
429
451
  fail ArgumentError.new(
@@ -436,12 +458,13 @@ module Rufus
436
458
 
437
459
  def last_at=(last)
438
460
 
439
- @last_at = last ? Rufus::Scheduler.parse_to_time(last) : nil
461
+ #@last_at = last ? Rufus::Scheduler.parse_to_time(last) : nil
462
+ @last_at = last ? ZoTime.make(last) : nil
440
463
 
441
464
  fail ArgumentError.new(
442
465
  "cannot set last[_at|_in] in the past: " +
443
466
  "#{last.inspect} -> #{@last_at.inspect}"
444
- ) if last && @last_at < Time.now
467
+ ) if last && @last_at < Rufus::Scheduler::ZoTime.now
445
468
 
446
469
  @last_at
447
470
  end
@@ -453,7 +476,7 @@ module Rufus
453
476
  return (@next_time = nil) if @times && @times < 1
454
477
  return (@next_time = nil) if @last_at && time >= @last_at
455
478
  #
456
- # TODO: rework that, jobs are thus kept 1 step too much in @jobs
479
+ # It keeps jobs one step too much in @jobs, but it's OK
457
480
 
458
481
  super
459
482
 
@@ -462,7 +485,7 @@ module Rufus
462
485
 
463
486
  def pause
464
487
 
465
- @paused_at = Time.now
488
+ @paused_at = Rufus::Scheduler::ZoTime.now
466
489
  end
467
490
 
468
491
  def resume
@@ -513,9 +536,7 @@ module Rufus
513
536
 
514
537
  def first_at=(first)
515
538
 
516
- super
517
-
518
- @next_time = @first_at
539
+ @next_time = super
519
540
  end
520
541
  end
521
542
 
@@ -543,10 +564,10 @@ module Rufus
543
564
 
544
565
  return if is_post
545
566
 
546
- n = Time.now
567
+ n = Rufus::Scheduler::ZoTime.now
547
568
 
548
569
  @next_time =
549
- if @first_at == nil || @first_at < n
570
+ if @first_at == nil || @first_at < (n - @scheduler.frequency)
550
571
  nt = (@next_time || trigger_time || n) + @frequency
551
572
  nt > n ? nt : (trigger_time || n) + @frequency
552
573
  else
@@ -584,10 +605,10 @@ module Rufus
584
605
 
585
606
  @next_time =
586
607
  if is_post
587
- Time.now + @interval
608
+ Rufus::Scheduler::ZoTime.now + @interval
588
609
  elsif trigger_time.nil?
589
610
  if @first_at == nil || @first_at < Time.now
590
- Time.now + @interval
611
+ Rufus::Scheduler::ZoTime.now + @interval
591
612
  else
592
613
  @first_at
593
614
  end
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2006-2015, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2006-2016, 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
@@ -41,7 +41,7 @@ class Rufus::Scheduler
41
41
  end
42
42
 
43
43
  #
44
- # The standard flock mecha, with its own class thanks to @ecin
44
+ # The standard flock mechanism, with its own class thanks to @ecin
45
45
  #
46
46
  class FileLock
47
47