lib/time/cron.rb in bblib-0.3.0 vs lib/time/cron.rb in bblib-0.4.1
- old
+ new
@@ -1,171 +1,213 @@
+# frozen_string_literal: true
module BBLib
-
class Cron
- attr_reader :exp, :parts, :time
+ include Effortless
+ attr_str :expression, default: '* * * * * *'
+ attr_reader :parts, serialize: false
- def initialize exp = '* * * * * *'
- @parts = Hash.new
- self.exp = exp
+ def next(exp = expression, count: 1, time: Time.now)
+ self.expression = exp unless exp == expression
+ closest(count: count, time: time, direction: 1)
end
- def closest exp = @exp, direction:1, count: 1, time: Time.now
- if exp then self.exp = exp end
- results = []
- return results unless @exp
- (1..count).each{ |i| results.push next_time(i == 1 ? time : results.last, direction) }
- count <= 1 ? results.first : results.reject{ |r| r.nil? }
+ def prev(exp = expression, count: 1, time: Time.now)
+ self.expression = exp unless exp == expression
+ closest(count: count, time: time, direction: -1)
end
- def next exp = @exp, count: 1, time: Time.now
- closest exp, count:count, time:time, direction:1
+ def expression=(e)
+ e = e.to_s.downcase
+ SPECIAL_EXP.each { |x, v| e = x if v.include?(e) }
+ @expression = e
+ parse
+ e
end
- def prev exp = @exp, count: 1, time: Time.now
- closest exp, count:count, time:time, direction:-1
+ def self.next(exp, count: 1, time: Time.now)
+ BBLib::Cron.new(exp).next(count: count, time: time)
end
- def exp= e
- SPECIAL_EXP.each{ |x, v| if v.include?(e) then e = x end }
- @exp = e
- parse
+ def self.prev(exp, count: 1, time: Time.now)
+ BBLib::Cron.new(exp).prev(count: count, time: time)
end
- def self.next exp, count: 1, time: Time.now
- t = BBLib::Cron.new(exp).next(count:count, time:time)
+ def self.valid?(exp)
+ !(numeralize(exp) =~ /\A(.*?\s){4,5}.*?\S\z/).nil?
end
- def self.prev exp, count: 1, time: Time.now
- BBLib::Cron.new(exp).prev(count:count, time:time)
+ def valid?(exp)
+ BBLib::Cron.valid?(exp)
end
- def self.valid? exp
- !(numeralize(exp) =~ /\A(.*?\s){4,5}.*?\S\z/).nil?
+ def self.numeralize(exp)
+ REPLACE.each do |k, v|
+ v.each do |r|
+ exp = exp.to_s.gsub(r.to_s, k.to_s)
+ end
+ end
+ exp
end
- def valid? exp
- BBLib::Cron.valid?(exp)
+ def time_match?(time)
+ (@parts[:minute].empty? || @parts[:minute].include?(time.min)) &&
+ (@parts[:hour].empty? || @parts[:hour].include?(time.hour)) &&
+ (@parts[:day].empty? || @parts[:day].include?(time.day)) &&
+ (@parts[:weekday].empty? || @parts[:weekday].include?(time.wday)) &&
+ (@parts[:month].empty? || @parts[:month].include?(time.month)) &&
+ (@parts[:year].empty? || @parts[:year].include?(time.year))
end
private
- def parse
- return nil unless @exp
- pieces, i = @exp.split(' '), 0
- PARTS.each do |part, info|
- @parts[part] = parse_cron_numbers(pieces[i], info[:min], info[:max], Time.now.send(info[:send]))
- i+=1
- end
+ def simple_init(*args)
+ @parts = {}
+ self.expression = args.first if args.first.is_a?(String)
+ end
+
+ def parse
+ @parts = {}
+ PARTS.keys.zip(@expression.split(' ')).to_h.each do |part, piece|
+ info = PARTS[part]
+ @parts[part] = parse_cron_numbers(piece, info[:min], info[:max], Time.now.send(info[:send]))
end
+ end
- def self.numeralize exp
- exp = exp.to_s.downcase
- REPLACE.each do |k, v|
- v.each do |r|
- exp = exp.gsub(r.to_s, k.to_s)
- end
+ def parse_cron_numbers(exp, min, max, qmark)
+ numbers = []
+ return numbers if exp == '*'
+ exp = Cron.numeralize(exp).gsub('?', qmark.to_s).gsub('*', "#{min}-#{max}")
+ exp.scan(/\*\/\d+|\d+\/\d+|\d+-\d+\/\d+/).each do |s|
+ range = s.split('/').first.split('-').map(&:to_i) + [max]
+ divisor = s.split('/').last.to_i
+ Range.new(*range[0..1]).each_with_index do |i, index|
+ numbers.push(i) if index.zero? || (index % divisor).zero?
end
- exp
+ exp = exp.sub(s, '')
end
+ exp.scan(/\d+\-\d+/).each do |e|
+ nums = e.scan(/\d+/).map(&:to_i)
+ numbers.push(Range.new(*nums).to_a)
+ end
+ numbers.push(exp.scan(/\d+/).map(&:to_i))
+ numbers.flatten.uniq.sort.reject { |r| r < min || r > max }
+ end
- def parse_cron_numbers exp, min, max, qmark
- numbers = Array.new
- exp = Cron.numeralize(exp)
- exp = exp.gsub('?', qmark.to_s)
- exp.scan(/\*\/\d+|\d+\/\d+|\d+-\d+\/\d+/).each do |s|
- range, divisor = s.split('/').first, s.split('/').last.to_i
- if range == '*'
- range = (min..max)
- elsif range =~ /\d+\-\d+/
- range = (range.split('-').first.to_i..range.split('-').last.to_i)
- else
- range = (range.to_i..max)
- end
- index = 0
- range.each do |i|
- if index == 0 || index % divisor.to_i == 0
- numbers.push i
- end
- index+=1
- end
- exp = exp.sub(s, '')
- end
- numbers.push exp.scan(/\d+/).map{ |m| m.to_i }
- exp.strip.scan(/\d+\-\d+/).each do |e|
- nums = e.scan(/\d+/).map{ |n| n.to_i }
- numbers.push (nums.min..nums.max).map{ |n| n }
- end
- numbers.flatten.sort.uniq.reject{ |r| r < min || r > max }
+ def closest(direction: 1, count: 1, time: Time.now)
+ return unless @expression
+ results = (1..count).flat_map do |_i|
+ time = next_time(time + 60 * direction, direction)
end
+ count <= 1 ? results.first : results.compact
+ end
- def next_day time, direction
- return nil unless time
- weekdays, days, months, years = @parts[:weekday], @parts[:day], @parts[:month], @parts[:year]
- date, safety = nil, 0
- while date.nil? && safety < 50000
- if (days.empty? || days.include?(time.day)) && (months.empty? || months.include?(time.month)) && (years.empty? || years.include?(time.year)) && (weekdays.empty? || weekdays.include?(time.wday))
- date = time
- else
- time+= 24*60*60*direction
- # time = Time.new(time.year, time.month, time.day, 0, 0)
- end
- safety+=1
+ def next_time(time, direction)
+ original = time.dup
+ safety = 0
+ methods = [:next_year, :next_month, :next_weekday, :next_day, :next_hour, :next_min]
+ until safety >= 1_000_000 || time_match?(time)
+ methods.each do |sym|
+ time = send(sym, time, direction)
end
- return nil if safety == 50000
- time
+ safety += 1
end
+ time - (time.sec.zero? ? 0 : original.sec)
+ end
- def next_time time, direction
- orig, fw = time.to_f, (direction == 1)
- current = next_day(time, direction)
- return nil unless current
- if (fw ? current.to_f > orig : current.to_f < orig)
- current = Time.new(current.year, current.month, current.day, (fw ? 0 : 23), (fw ? 0 : 59))
- else
- current+= (fw ? 60 : -60)
+ def next_min(time, direction)
+ return time if @parts[:minute].empty?
+ time += 60 * direction until @parts[:minute].include?(time.min)
+ time
+ end
+
+ def next_hour(time, direction)
+ return time if @parts[:hour].empty?
+ until @parts[:hour].include?(time.hour)
+ time -= time.min * 60 if direction.positive?
+ time += (59 - time.min) * 60 if direction.negative?
+ time += 60*60 * direction
+ end
+ time
+ end
+
+ def next_day(time, direction)
+ return time if @parts[:day].empty?
+ time += 24*60*60 * direction until @parts[:day].include?(time.day)
+ time
+ end
+
+ def next_weekday(time, direction)
+ return time if @parts[:weekday].empty?
+ time += 24*60*60 * direction until @parts[:weekday].include?(time.wday)
+ time
+ end
+
+ def next_month(time, direction)
+ return time if @parts[:month].empty?
+ until @parts[:month].include?(time.month)
+ original = time.month
+ min = direction.positive? ? 0 : 59
+ hour = direction.positive? ? 0 : 23
+ day = direction.positive? ? 1 : 31
+ month = BBLib.loop_between(time.month + direction, 1, 12)
+ year = if direction.positive? && month == 1
+ time.year + 1
+ elsif direction.negative? && month == 12
+ time.year - 1
+ else
+ time.year
+ end
+ time = Time.new(year, month, day, hour, min)
+ if direction.negative? && time.month == original
+ time -= 24 * 60 * 60 while time.month == original
end
- while !@parts[:day].empty? && !@parts[:day].include?(current.day) || !@parts[:hour].empty? && !@parts[:hour].include?(current.hour) || !@parts[:minute].empty? && !@parts[:minute].include?(current.min)
- day = [current.day, current.month, current.year]
- current+= (fw ? 60 : -60)
- if day != [current.day, current.month, current.year] then current = next_day(current, direction) end
- return nil unless current
- end
- current - current.sec
end
+ time
+ end
- PARTS = {
- minute: {send: :min, min:0, max:59, size: 60},
- hour: {send: :hour, min:0, max:23, size: 60*60},
- day: {send: :day, min:1, max:31, size: 60*60*24},
- month: {send: :month, min:1, max:12},
- weekday: {send: :wday, min:0, max:6},
- year: {send: :year, min:0, max:90000}
- }
+ def next_year(time, direction)
+ return time if @parts[:year].empty?
+ until @parts[:year].include?(time.year)
+ day = direction.positive? ? 1 : 31
+ hour = direction.positive? ? 0 : 23
+ min = direction.positive? ? 0 : 59
+ month = direction.positive? ? 1 : 12
+ time = Time.new(time.year + direction, month, day, hour, min)
+ end
+ time
+ end
- REPLACE = {
- 0 => [:sunday, :sun],
- 1 => [:monday, :mon, :january, :jan],
- 2 => [:tuesday, :tues, :february, :feb],
- 3 => [:wednesday, :wednes, :tue, :march, :mar],
- 4 => [:thursday, :thurs, :wed, :april, :apr],
- 5 => [:friday, :fri, :thu, :may],
- 6 => [:saturday, :sat, :june, :jun],
- 7 => [:july, :jul],
- 8 => [:august, :aug],
- 9 => [:september, :sept, :sep],
- 10 => [:october, :oct],
- 11 => [:november, :nov],
- 12 => [:december, :dec]
- }
+ PARTS = {
+ minute: { send: :min, min: 0, max: 59, size: 60 },
+ hour: { send: :hour, min: 0, max: 23, size: 60*60 },
+ day: { send: :day, min: 1, max: 31, size: 60*60*24 },
+ month: { send: :month, min: 1, max: 12 },
+ weekday: { send: :wday, min: 0, max: 6 },
+ year: { send: :year, min: 0, max: 3_000 }
+ }.freeze
- SPECIAL_EXP = {
- '0 0 * * * *' => ['@daily', '@midnight', 'daily', 'midnight'],
- '0 12 * * * *' => ['@noon', 'noon'],
- '0 0 * * 0 *' => ['@weekly', 'weekly'],
- '0 0 1 * * *' => ['@monthly', 'monthly'],
- '0 0 1 1 * *' => ['@yearly', '@annually', 'yearly', 'annually'],
- '? ? ? ? ? ?' => ['@reboot', '@restart', 'reboot', 'restart']
- }
+ REPLACE = {
+ 0 => [:sunday, :sun],
+ 1 => [:monday, :mon, :january, :jan],
+ 2 => [:tuesday, :tues, :february, :feb],
+ 3 => [:wednesday, :wednes, :tue, :march, :mar],
+ 4 => [:thursday, :thurs, :wed, :april, :apr],
+ 5 => [:friday, :fri, :thu, :may],
+ 6 => [:saturday, :sat, :june, :jun],
+ 7 => [:july, :jul],
+ 8 => [:august, :aug],
+ 9 => [:september, :sept, :sep],
+ 10 => [:october, :oct],
+ 11 => [:november, :nov],
+ 12 => [:december, :dec]
+ }.freeze
+ SPECIAL_EXP = {
+ '0 0 * * * *' => ['@daily', '@midnight', 'daily', 'midnight'],
+ '0 12 * * * *' => ['@noon', 'noon'],
+ '0 0 * * 0 *' => ['@weekly', 'weekly'],
+ '0 0 1 * * *' => ['@monthly', 'monthly'],
+ '0 0 1 1 * *' => ['@yearly', '@annually', 'yearly', 'annually'],
+ '? ? ? ? ? ?' => ['@reboot', '@restart', 'reboot', 'restart']
+ }.freeze
end
-
end