cron_calc 0.3.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 65bbd980a990785461ad8513755d5ef1effc8d14f4dac4205bf0381f4489bbcd
4
- data.tar.gz: 116ac513ff00017a519997d1399748faa95284b2171e6f11c5097a25b10525ee
3
+ metadata.gz: aebec718fd55bb74df49580abe4309a41ad0246b2c266a143f9dfac807cd436e
4
+ data.tar.gz: 1b47dea7541f369800f1f816fdf2160e0721de4a4f8bcdd9434c0b77fdf85876
5
5
  SHA512:
6
- metadata.gz: 70d5d114af61a0430a52b8ef0fc06caecc693fe8f632472814f4335543b038ba6c94565069f831ee7486a8633e015e8833c65b6f1ba2814832a7bb5e4352c9fb
7
- data.tar.gz: cb5b23e742052e1b76035721214c28c2a9ce162b70d7c1d60bb308e378db36318c3e4ceac9e65b4a7dc779c1cc710109880f8b921f00580df77bf5f1cc8d3539
6
+ metadata.gz: f6f3349709b4a5babb3d2267f7e8cf1a7e2e51d9a118044e67743cc02b31d34bf5d637b068e825afe9f10c24e34c97a906730363a2c1c309eb003408b2c48586
7
+ data.tar.gz: fd80e141fd67e56adb122b6acf2dbbeff5572e69868212c749d1f6d67c422d70790a9910cec991a2ef328841f8fea17e2214c6276761124f5e4630f04c613557
data/CHANGELOG.md CHANGED
@@ -1,6 +1,16 @@
1
+ ## [1.0.0] - 2023-12-28
2
+
3
+ - Add support for predefined definitions
4
+ - Add support for joining ,-
5
+
6
+ ## [0.4.0] - 2023-12-22
7
+
8
+ - Added support for named months and wdays
9
+ - Keyword arguments: before:, after:
10
+
1
11
  ## [0.3.0] - 2023-12-22
2
12
 
3
- - Added support for DOWs
13
+ - Added support for DOWs (wdays)
4
14
 
5
15
  ## [0.2.0] - 2023-12-19
6
16
 
data/README.md CHANGED
@@ -1,6 +1,11 @@
1
1
  # CronCalc
2
2
 
3
- CronCalc: A Ruby gem for calculating and analyzing scheduled CRON job occurrences and timings within specified intervals.
3
+ **CronCalc**: A Ruby gem for calculating CRON job occurrences. With this gem, you can easily determine when a cron job will occur by providing a cron expression. Key features include the ability to **calculate occurrences**:
4
+ - **Within a specified period**: Find out all the times your cron job will run during a particular timeframe.
5
+ - **After a given date**: Determine the next set of occurrences after a specific starting point.
6
+ - **Before a given date**: Discover when your cron job ran or would have run before a certain date.
7
+
8
+ This tool can be used for scheduling, forecasting, and analyzing tasks in systems that use cron for job scheduling.
4
9
 
5
10
  ## Installation
6
11
 
@@ -22,8 +27,9 @@ Now, you can use one of three methods `#in`, `#next`, `#last` to determine cron
22
27
  ### Using `#in`
23
28
 
24
29
  Calculates cron job occurrences within a given time period.\
25
- @param period [Range] The time period for which to calculate cron job occurrences.\
26
- @return [Array<Time>] An array of Time instances representing each occurrence.
30
+ **Parameters:**
31
+ - `period` - a Range object defining the start and end times for the calculation.\
32
+
27
33
 
28
34
  ```ruby
29
35
  period = Time.new(2024, 1, 1, 0, 0)..Time.new(2024, 1, 4, 0, 0)
@@ -35,10 +41,10 @@ Calculates cron job occurrences within a given time period.\
35
41
  ### Using `#next`
36
42
 
37
43
  Calculates the next 'n' occurrences of the cron job from a given start time.\
38
- @param count [Integer] The number of occurrences to calculate. Defaults to `1`.\
39
- @param period_start [Time] The start time from which to calculate occurrences. Defaults to `Time.now`.\
40
- @param max_years [Integer] The maximum number of years to consider for the period. Defaults to `5`.\
41
- @return [Array<Time>] An array of the next 'n' occurrences.
44
+ **Parameters:**
45
+ - `count` - (optional, Integer) The number of occurrences to calculate. Defaults to 1.
46
+ - `after:` - (optional, Time, keyword argument) The start time from which to calculate occurrences. If not provided, defaults to the current time (Time.now).
47
+ - `max_years` - (optional, Integer, keyword argument) The maximum number of years to search for future occurrences. Defaults to 5.
42
48
 
43
49
  ```ruby
44
50
  cron_calc.next
@@ -47,31 +53,41 @@ Calculates the next 'n' occurrences of the cron job from a given start time.\
47
53
  cron_calc.next(3)
48
54
  # => [2023-12-20 05:05:00 +0100, 2023-12-21 05:05:00 +0100, 2023-12-22 05:05:00 +0100]
49
55
 
50
- cron_calc.next(2, Time.new(2024, 1, 1, 0, 0))
56
+ cron_calc.next(2, after: Time.new(2024, 1, 1, 0, 0))
51
57
  # => [2024-01-01 05:05:00 +0100, 2024-01-02 05:05:00 +0100]
52
58
  ```
53
59
 
54
60
  ### Using `#last`
55
61
 
56
62
  Calculates the last 'n' occurrences of the cron job until a given end time.\
57
- @param count [Integer] The number of past occurrences to calculate.\
58
- @param period_end [Time] The end time until which to calculate occurrences.\
59
- @param max_years [Integer] The maximum number of years to consider for the period.\
60
- @return [Array<Time>] An array of the last 'n' occurrences.
63
+ **Parameters:**
64
+ - `count` - (optional, Integer) The number of occurrences to calculate. Defaults to 1.
65
+ - `before:` - (optional, Time, keyword argument) The end time from which to calculate past occurrences. If not provided, defaults to the current time (Time.now).
66
+ - `max_years` - (optional, Integer, keyword argument) The maximum number of years to search backward for past occurrences. Defaults to 5.
61
67
 
62
68
  ```ruby
63
69
  cron_calc.last
64
70
  # => [2023-12-19 05:05:00 +0100]
65
71
 
66
- cron_calc.last(4, Time.new(2024, 1, 1, 0, 0))
72
+ cron_calc.last(4, before: Time.new(2024, 1, 1, 0, 0))
67
73
  # => [2023-12-31 05:05:00 +0100, 2023-12-30 05:05:00 +0100, 2023-12-29 05:05:00 +0100, 2023-12-28 05:05:00 +0100]
68
74
  ```
69
75
 
70
- ## Unsupported features
76
+ ### Other examples
77
+
78
+ ```ruby
79
+ # You can omit the count parameter
80
+ CronCalc.new('5 5 */5 * SUN').last(before: Time.new(2020, 1, 1, 0, 0))
81
+ # => [2019-12-01 05:05:00 +0100]
82
+
83
+ # You can combine ',' and '-'
84
+ CronCalc.new('5 5 5-7,10 FEB *').next(5)
85
+ # => [2024-02-05 05:05:00 +0100, 2024-02-06 05:05:00 +0100, 2024-02-07 05:05:00 +0100, 2024-02-10 05:05:00 +0100, 2025-02-05 05:05:00 +0100]
71
86
 
72
- - Named DOW and months (SUN-SAT, JAN-DEC)
73
- - Joining characters , - /
74
- - Predefined definitions (@yearly, @monthly, @weekly, @daily, @midnight, @hourly)
87
+ # You can use predefined definitions like @daily, @monthly, etc.
88
+ CronCalc.new('@monthly').next(3, after: Time.new(2024, 1, 1, 0, 0))
89
+ # => [2024-01-01 00:00:00 +0100, 2024-02-01 00:00:00 +0100, 2024-03-01 00:00:00 +0100]
90
+ ```
75
91
 
76
92
  ## Contributing
77
93
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CronCalc
4
- VERSION = '0.3.0'
4
+ VERSION = '1.0.0'
5
5
  end
data/lib/cron_calc.rb CHANGED
@@ -7,6 +7,33 @@ require 'time'
7
7
  module CronCalc
8
8
  class Error < StandardError; end
9
9
 
10
+ RANGE = {
11
+ minutes: 0..59,
12
+ hours: 0..23,
13
+ days: 1..31,
14
+ months: 1..12,
15
+ wdays: 0..6
16
+ }.freeze
17
+
18
+ WDAYS = {
19
+ 'SUN' => '0', 'MON' => '1', 'TUE' => '2', 'WED' => '3',
20
+ 'THU' => '4', 'FRI' => '5', 'SAT' => '6'
21
+ }.freeze
22
+
23
+ MONTHS = {
24
+ 'JAN' => '1', 'FEB' => '2', 'MAR' => '3', 'APR' => '4',
25
+ 'MAY' => '5', 'JUN' => '6', 'JUL' => '7', 'AUG' => '8',
26
+ 'SEP' => '9', 'OCT' => '10', 'NOV' => '11', 'DEC' => '12'
27
+ }.freeze
28
+
29
+ PREDEFINED_DEFINITIONS = {
30
+ '@yearly' => '0 0 1 1 *', '@annually' => '0 0 1 1 *',
31
+ '@monthly' => '0 0 1 * *',
32
+ '@weekly' => '0 0 * * 0',
33
+ '@daily' => '0 0 * * *', '@midnight' => '0 0 * * *',
34
+ '@hourly' => '0 * * * *'
35
+ }.freeze
36
+
10
37
  def self.new(cron_string)
11
38
  Parser.new(cron_string)
12
39
  end
@@ -17,19 +44,12 @@ module CronCalc
17
44
  class Parser
18
45
  attr_reader :cron_string, :cron_parts
19
46
 
20
- RANGE = {
21
- minutes: 0..59,
22
- hours: 0..23,
23
- days: 1..31,
24
- months: 1..12,
25
- dows: 0..7
26
- }.freeze
27
-
28
47
  def initialize(cron_string)
29
48
  @cron_string = cron_string
30
49
 
31
50
  raise 'Cron expression is not supported or invalid' unless cron_string_valid?
32
51
 
52
+ @cron_string = normalize_with(cron_string, PREDEFINED_DEFINITIONS) if @cron_string.start_with? '@'
33
53
  @cron_parts = split_cron_string
34
54
  end
35
55
 
@@ -42,24 +62,24 @@ module CronCalc
42
62
 
43
63
  # Calculates the next 'n' occurrences of the cron job from a given start time.
44
64
  # @param count [Integer] The number of occurrences to calculate.
45
- # @param period_start [Time] The start time from which to calculate occurrences.
65
+ # @param after [Time] The start time from which to calculate occurrences.
46
66
  # @param max_years [Integer] The maximum number of years to consider for the period.
47
67
  # @return [Array<Time>] An array of the next 'n' occurrences.
48
- def next(count = 1, period_start = Time.now, max_years = 5)
68
+ def next(count = 1, after: Time.now, max_years: 5)
49
69
  occurrences(
50
- period_start..(period_start + (60 * 60 * 24 * 365 * max_years)),
70
+ after..(after + (60 * 60 * 24 * 365 * max_years)),
51
71
  count
52
72
  )
53
73
  end
54
74
 
55
75
  # Calculates the last 'n' occurrences of the cron job until a given end time.
56
76
  # @param count [Integer] The number of past occurrences to calculate.
57
- # @param period_end [Time] The end time until which to calculate occurrences.
77
+ # @param before [Time] The end time until which to calculate occurrences.
58
78
  # @param max_years [Integer] The maximum number of years to consider for the period.
59
79
  # @return [Array<Time>] An array of the last 'n' occurrences.
60
- def last(count = 1, period_end = Time.now, max_years = 5)
80
+ def last(count = 1, before: Time.now, max_years: 5)
61
81
  occurrences(
62
- (period_end - (60 * 60 * 24 * 365 * max_years))..period_end,
82
+ (before - (60 * 60 * 24 * 365 * max_years))..before,
63
83
  count,
64
84
  reverse: true
65
85
  )
@@ -69,7 +89,7 @@ module CronCalc
69
89
 
70
90
  def occurrences(period, count = nil, reverse: false)
71
91
  time_combinations = generate_time_combinations(period, reverse).lazy
72
- wdays = parse_cron_part(:dows)
92
+ wdays = parse_cron_part(:wdays)
73
93
 
74
94
  time_combinations.each_with_object([]) do |(year, month, day, hour, minute), occ|
75
95
  break occ if count && occ.length == count
@@ -94,8 +114,8 @@ module CronCalc
94
114
  minutes: splitted[0],
95
115
  hours: splitted[1],
96
116
  days: splitted[2],
97
- months: splitted[3],
98
- dows: splitted[4]
117
+ months: normalize_with(splitted[3], MONTHS),
118
+ wdays: normalize_with(splitted[4], WDAYS)
99
119
  }
100
120
  end
101
121
 
@@ -103,31 +123,40 @@ module CronCalc
103
123
  %i[minutes hours days months].map { |unit| parse_cron_part(unit) }
104
124
  end
105
125
 
106
- # rubocop:disable Metrics
107
126
  def parse_cron_part(time_unit)
108
127
  range = RANGE[time_unit]
109
128
  part = cron_parts[time_unit]
110
129
 
111
- case part
130
+ if part.include?(',')
131
+ part.split(',').flat_map { |e| parse_single_element(e, range) }.uniq.sort
132
+ else
133
+ parse_single_element(part, range)
134
+ end
135
+ end
136
+
137
+ def parse_single_element(element, range)
138
+ case element
112
139
  when '*'
113
140
  range.to_a
114
- when /,/
115
- part.split(',').map(&:to_i)
116
141
  when /-/
117
- (part.split('-').first.to_i..part.split('-').last.to_i).to_a
142
+ (element.split('-').first.to_i..element.split('-').last.to_i).to_a
118
143
  when %r{/}
119
- range.step(part.split('/').last.to_i).to_a
144
+ range.step(element.split('/').last.to_i).to_a
120
145
  else
121
- [part.to_i]
146
+ [element.to_i]
122
147
  end
123
148
  end
124
- # rubocop:enable Metrics
149
+
150
+ def normalize_with(string, mapping)
151
+ mapping.inject(string) { |str, (k, v)| str.gsub(k, v) }
152
+ end
125
153
 
126
154
  def cron_string_valid?
155
+ predefined = /\A@(yearly|annually|monthly|weekly|daily|midnight|hourly)\z/
127
156
  # rubocop:disable Layout/LineLength
128
- 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}
157
+ standard = %r{\A(\*|(\*/[0-5]?\d)|([0-5]?\d)(-(?:[0-5]?\d))?(,([0-5]?\d)(-(?:[0-5]?\d))?)*) (\*|(\*/[01]?\d|2[0-3])|([01]?\d|2[0-3])(-(?:[01]?\d|2[0-3]))?(,([01]?\d|2[0-3])(-(?:[01]?\d|2[0-3]))?)*|(\*/\d+)) (\*|(\*/[12]?\d|3[01])|([12]?\d|3[01])(-(?:[12]?\d|3[01]))?(,([12]?\d|3[01])(-(?:[12]?\d|3[01]))?)*|(\*/\d+)) (\*|(\*/[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|[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|[1-9]|1[0-2]))?)*|(\*/\d+)) (\*|(\*/[0-6])|(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])(-(?:SUN|MON|TUE|WED|THU|FRI|SAT|[0-6]))?)*|(\*/[0-6]+))\z}
129
158
  # rubocop:enable Layout/LineLength
130
- cron_string.match?(regex)
159
+ cron_string.match?(predefined) || cron_string.match?(standard)
131
160
  end
132
161
  end
133
162
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cron_calc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jakub Miziński
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-12-22 00:00:00.000000000 Z
11
+ date: 2023-12-28 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |-
14
14
  Calculates cron job occurrences within a specified period