diff --git a/README.md b/README.md index 43e9261..970fcef 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,6 @@ Calculates the last 'n' occurrences of the cron job until a given end time.\ ## Unsupported features -- Named DOW and months (SUN-SAT, JAN-DEC) - Joining characters , - / - Predefined definitions (@yearly, @monthly, @weekly, @daily, @midnight, @hourly) diff --git a/lib/cron_calc.rb b/lib/cron_calc.rb index 05b2067..bf34882 100644 --- a/lib/cron_calc.rb +++ b/lib/cron_calc.rb @@ -2,6 +2,7 @@ require_relative 'cron_calc/version' require 'time' +require 'debug' # The CronCalc is gem-wrapper module for the Parser class module CronCalc @@ -22,7 +23,18 @@ class Parser hours: 0..23, days: 1..31, months: 1..12, - dows: 0..7 + wdays: 0..6 + }.freeze + + WDAYS = { + 'SUN' => '0', 'MON' => '1', 'TUE' => '2', 'WED' => '3', + 'THU' => '4', 'FRI' => '5', 'SAT' => '6' + }.freeze + + MONTHS = { + 'JAN' => '1', 'FEB' => '2', 'MAR' => '3', 'APR' => '4', + 'MAY' => '5', 'JUN' => '6', 'JUL' => '7', 'AUG' => '8', + 'SEP' => '9', 'OCT' => '10', 'NOV' => '11', 'DEC' => '12' }.freeze def initialize(cron_string) @@ -69,7 +81,7 @@ def last(count = 1, period_end = Time.now, max_years = 5) def occurrences(period, count = nil, reverse: false) time_combinations = generate_time_combinations(period, reverse).lazy - wdays = parse_cron_part(:dows) + wdays = parse_cron_part(:wdays) time_combinations.each_with_object([]) do |(year, month, day, hour, minute), occ| break occ if count && occ.length == count @@ -94,8 +106,8 @@ def split_cron_string minutes: splitted[0], hours: splitted[1], days: splitted[2], - months: splitted[3], - dows: splitted[4] + months: normalize_with(splitted[3], MONTHS), + wdays: normalize_with(splitted[4], WDAYS) } end @@ -123,9 +135,13 @@ def parse_cron_part(time_unit) end # rubocop:enable Metrics + def normalize_with(string, mapping) + mapping.inject(string) { |str, (k, v)| str.gsub(k, v) } + end + def cron_string_valid? # rubocop:disable Layout/LineLength - regex = %r{\A(\*|([0-5]?\d)(,([0-5]?\d))*|(\*/\d+)|(\d+-\d+)) (\*|([01]?\d|2[0-3])(,([01]?\d|2[0-3]))*|(\*/\d+)|(\d+-\d+)) (\*|([12]?\d|3[01])(,([12]?\d|3[01]))*|(\*/\d+)|(\d+-\d+)) (\*|([1-9]|1[0-2])(,([1-9]|1[0-2]))*|(\*/\d+)|(\d+-\d+)) (\*|([0-6])(,([0-6]))*|(\*/[0-6]+)|([0-6]-[0-6]))\z} + regex = %r{\A(\*|([0-5]?\d)(,([0-5]?\d))*|(\*/\d+)|(\d+-\d+)) (\*|([01]?\d|2[0-3])(,([01]?\d|2[0-3]))*|(\*/\d+)|(\d+-\d+)) (\*|([12]?\d|3[01])(,([12]?\d|3[01]))*|(\*/\d+)|(\d+-\d+)) (\*|(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC|[1-9]|1[0-2])(,(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC|[1-9]|1[0-2])|-(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))*|(\*/\d+)|(\d+-\d+)) (\*|(SUN|MON|TUE|WED|THU|FRI|SAT|[0-6])(,(SUN|MON|TUE|WED|THU|FRI|SAT|[0-6])|-(SUN|MON|TUE|WED|THU|FRI|SAT))*|(\*/[0-6]+)|([0-6]-[0-6]))\z} # rubocop:enable Layout/LineLength cron_string.match?(regex) end diff --git a/spec/cron_calc_spec.rb b/spec/cron_calc_spec.rb index bdb7f6f..7cd4693 100644 --- a/spec/cron_calc_spec.rb +++ b/spec/cron_calc_spec.rb @@ -132,7 +132,7 @@ end end - context 'when DOW excludes days of month' do + context 'when wdays excludes days of month' do let(:cron_string) { '5 5 14-22 * 0,6' } let(:period) { Time.new(2024, 1, 1, 0, 0)..Time.new(2024, 2, 1, 0, 0) } @@ -144,6 +144,32 @@ ]) end end + + context 'when named months are used' do + let(:cron_string) { '5 5 5 JAN,FEB *' } + let(:period) { Time.new(2024, 1, 1, 0, 0)..Time.new(2024, 3, 31, 0, 0) } + + it do + expect(subject).to eq([ + Time.new(2024, 1, 5, 5, 5), + Time.new(2024, 2, 5, 5, 5) + ]) + end + end + + context 'when named wdays are used' do + let(:cron_string) { '5 5 1-8 FEB TUE-THU' } + let(:period) { Time.new(2024, 1, 1, 0, 0)..Time.new(2024, 3, 31, 0, 0) } + + it do + expect(subject).to eq([ + Time.new(2024, 2, 1, 5, 5), + Time.new(2024, 2, 6, 5, 5), + Time.new(2024, 2, 7, 5, 5), + Time.new(2024, 2, 8, 5, 5) + ]) + end + end end describe '#next' do