cron_calc 0.3.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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