diff --git a/lib/cron_parser.rb b/lib/cron_parser.rb index 41a8a5e..9ac301b 100644 --- a/lib/cron_parser.rb +++ b/lib/cron_parser.rb @@ -136,28 +136,40 @@ def last(now = @time_source.now, num=1) end - SUBELEMENT_REGEX = %r{^(\d+)(-(\d+)(/(\d+))?)?$} + SUBELEMENT_REGEX = %r{^(\d+)(-(\d+)(/(\d+))?)?(#(\d+))?$} def parse_element(elem, allowed_range) + extensions = Hash.new([]) + 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 + sub_values = + if subel =~ /^\*/ + ext = '*' + step = subel.length > 1 ? subel[2..-1].to_i : 1 + stepped_range(allowed_range, step) else - raise ArgumentError, "Bad Vixie-style specification #{subel}" + if SUBELEMENT_REGEX === subel + ext = $7.to_i if $7 + + 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 + + if ext + sub_values.each { |v| extensions[v].push(ext) } end + + sub_values end.flatten.sort - [Set.new(values), values, elem] + [Set.new(values), values, elem, extensions] end @@ -180,7 +192,7 @@ def interpolate_weekdays(year, month) 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] + valid_wday, _, wday_field, extensions = 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: @@ -192,8 +204,16 @@ def interpolate_weekdays_without_cache(year, month) valid_wday << 0 if valid_wday.include?(7) result = [] + wday_count = 0 while t.month == month - result << t.mday if valid_mday.include?(t.mday) || valid_wday.include?(t.wday) + ext = extensions[t.wday] + if valid_wday.include?(t.wday) + wday_count += 1 + result << t.mday if ext.empty? || ext.include?('*') || ext.include?(wday_count) + end + + result << t.mday if valid_mday.include?(t.mday) + t = t.succ end diff --git a/spec/cron_parser_spec.rb b/spec/cron_parser_spec.rb index 94f7ad0..1f5c576 100644 --- a/spec/cron_parser_spec.rb +++ b/spec/cron_parser_spec.rb @@ -78,6 +78,12 @@ def parse_date(str) ["15-59/15 * * * *", "2014-02-01 15:45", "2014-02-01 16:15",4], ["15-59/15 * * * *", "2014-02-01 15:46", "2014-02-01 16:15",3], ["15-59/15 * * * *", "2014-02-01 15:46", "2014-02-01 16:15",2], + ["0 0 * * 2 *", "2016-08-01 00:00", "2016-08-02 00:00",1], + ["0 0 * * 2#1 *", "2016-08-01 00:00", "2016-08-02 00:00",1], + ["0 0 * * 2#2 *", "2016-08-01 00:00", "2016-08-09 00:00",1], + ["0 0 * * 2#3 *", "2016-08-01 00:00", "2016-08-16 00:00",1], + ["0 0 * * 2#4 *", "2016-08-01 00:00", "2016-08-23 00:00",1], + ["0 0 * * 2#5 *", "2016-08-01 00:00", "2016-08-30 00:00",1], ].each do |line, now, expected_next,num| it "returns #{expected_next} for '#{line}' when now is #{now}" do parsed_now = parse_date(now)