class CronParser
Parses cron expressions and computes the next occurence of the “job”
Constants
- SUBELEMENT_REGEX
- SYMBOLS
Public Class Methods
Source
# File lib/cron_parser.rb, line 54 def initialize(source,time_source = Time) @source = interpret_vixieisms(source) @time_source = time_source validate_source end
Public Instance Methods
Source
# File lib/cron_parser.rb, line 60 def interpret_vixieisms(spec) case spec when '@reboot' raise ArgumentError, "Can't predict last/next run of @reboot" when '@yearly', '@annually' '0 0 1 1 *' when '@monthly' '0 0 1 * *' when '@weekly' '0 0 * * 0' when '@daily', '@midnight' '0 0 * * *' when '@hourly' '0 * * * *' else spec end end
Source
# File lib/cron_parser.rb, line 105 def last(now = @time_source.now) t = InternalTime.new(now,@time_source) unless time_specs[:month][0].include?(t.month) nudge_month(t, :last) t.day = 32 end if t.day == 32 || !interpolate_weekdays(t.year, t.month)[0].include?(t.day) nudge_date(t, :last) t.hour = 24 end unless time_specs[:hour][0].include?(t.hour) nudge_hour(t, :last) t.min = 60 end # always nudge the minute nudge_minute(t, :last) t.to_time end
returns the last occurence before the given date
Source
# File lib/cron_parser.rb, line 81 def next(now = @time_source.now) t = InternalTime.new(now, @time_source) unless time_specs[:month][0].include?(t.month) nudge_month(t) t.day = 0 end unless interpolate_weekdays(t.year, t.month)[0].include?(t.day) nudge_date(t) t.hour = -1 end unless time_specs[:hour][0].include?(t.hour) nudge_hour(t) t.min = -1 end # always nudge the minute nudge_minute(t) t.to_time end
returns the next occurence after the given date
Source
# File lib/cron_parser.rb, line 130 def parse_element(elem, allowed_range) values = elem.split(',').map do |subel| if subel =~ /^\*/ step = subel.length > 1 ? subel[2..-1].to_i : 1 stepped_range(allowed_range, step) else if SUBELEMENT_REGEX === subel if $5 # with range stepped_range($1.to_i..$3.to_i, $5.to_i) elsif $3 # range without step stepped_range($1.to_i..$3.to_i, 1) else # just a numeric [$1.to_i] end else raise ArgumentError, "Bad Vixie-style specification #{subel}" end end end.flatten.sort [Set.new(values), values, elem] end
Protected Instance Methods
Source
# File lib/cron_parser.rb, line 201 def date_valid?(t, dir = :next) interpolate_weekdays(t.year, t.month)[0].include?(t.day) end
Source
# File lib/cron_parser.rb, line 263 def find_best_next(current, allowed, dir) if dir == :next allowed.sort.find { |val| val > current } else allowed.sort.reverse.find { |val| val < current } end end
returns the smallest element from allowed which is greater than current returns nil if no matching value was found
Source
Source
# File lib/cron_parser.rb, line 162 def interpolate_weekdays_without_cache(year, month) t = Date.new(year, month, 1) valid_mday, _, mday_field = time_specs[:dom] valid_wday, _, wday_field = time_specs[:dow] # Careful, if both DOW and DOM fields are non-wildcard, # then we only need to match *one* for cron to run the job: if not (mday_field == '*' and wday_field == '*') valid_mday = [] if mday_field == '*' valid_wday = [] if wday_field == '*' end # Careful: crontabs may use either 0 or 7 for Sunday: valid_wday << 0 if valid_wday.include?(7) result = [] while t.month == month result << t.mday if valid_mday.include?(t.mday) || valid_wday.include?(t.wday) t = t.succ end [Set.new(result), result] end
Source
# File lib/cron_parser.rb, line 205 def nudge_date(t, dir = :next, can_nudge_month = true) spec = interpolate_weekdays(t.year, t.month)[1] next_value = find_best_next(t.day, spec, dir) t.day = next_value || (dir == :next ? spec.first : spec.last) nudge_month(t, dir) if next_value.nil? && can_nudge_month end
Source
# File lib/cron_parser.rb, line 213 def nudge_hour(t, dir = :next) spec = time_specs[:hour][1] next_value = find_best_next(t.hour, spec, dir) t.hour = next_value || (dir == :next ? spec.first : spec.last) nudge_date(t, dir) if next_value.nil? end
Source
# File lib/cron_parser.rb, line 221 def nudge_minute(t, dir = :next) spec = time_specs[:minute][1] next_value = find_best_next(t.min, spec, dir) t.min = next_value || (dir == :next ? spec.first : spec.last) nudge_hour(t, dir) if next_value.nil? end
Source
# File lib/cron_parser.rb, line 189 def nudge_month(t, dir = :next) spec = time_specs[:month][1] next_value = find_best_next(t.month, spec, dir) t.month = next_value || (dir == :next ? spec.first : spec.last) nudge_year(t, dir) if next_value.nil? # we changed the month, so its likely that the date is incorrect now valid_days = interpolate_weekdays(t.year, t.month)[1] t.day = dir == :next ? valid_days.first : valid_days.last end
Source
# File lib/cron_parser.rb, line 185 def nudge_year(t, dir = :next) t.year = t.year + (dir == :next ? 1 : -1) end
Source
# File lib/cron_parser.rb, line 250 def stepped_range(rng, step = 1) len = rng.last - rng.first num = len.div(step) result = (0..num).map { |i| rng.first + step * i } result.pop if result[-1] == rng.last and rng.exclude_end? result end
Source
# File lib/cron_parser.rb, line 243 def substitute_parse_symbols(str) SYMBOLS.inject(str.downcase) do |s, (symbol, replacement)| s.gsub(symbol, replacement) end end
Source
# File lib/cron_parser.rb, line 229 def time_specs @time_specs ||= begin # tokens now contains the 5 fields tokens = substitute_parse_symbols(@source).split(/\s+/) { :minute => parse_element(tokens[0], 0..59), #minute :hour => parse_element(tokens[1], 0..23), #hour :dom => parse_element(tokens[2], 1..31), #DOM :month => parse_element(tokens[3], 1..12), #mon :dow => parse_element(tokens[4], 0..6) #DOW } end end
Source
# File lib/cron_parser.rb, line 271 def validate_source unless @source.respond_to?(:split) raise ArgumentError, 'not a valid cronline' end source_length = @source.split(/\s+/).length unless source_length >= 5 && source_length <= 6 raise ArgumentError, 'not a valid cronline' end end