philiprehberger-cron_parser 0.2.0 → 0.4.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: eeb369c781ff5bf8c316b443bf97ee4786ed74e11842f7f5de81a9071418bdb2
4
- data.tar.gz: ee7c9d64bae3aee0f9a7b617197e32c6af9816d09ad372c80031d748082f9ab2
3
+ metadata.gz: 3be8924f48f35f97ea4d356f5368d7907cf23bda63af8af43c4b3776f1a47a69
4
+ data.tar.gz: 0d0284d3e84fe3b93db4911a30149333bf0faaba73f2857ec28ae95f83455509
5
5
  SHA512:
6
- metadata.gz: c95125f661ce1799d954dc8766c494b784db4fcc7ccd1ad37ca638b4cd3a6a807560fa9640868d2a9eb91e89e8323604868a3f27fecf797324571f7d6d341b48
7
- data.tar.gz: c1464aa92d9df184418b6e211864f2ea30c7444822c955c5772482e2c7e24d10c5dc395de909c4baf7e17436cee4aba87c20a7ff72fd3241ab3c5049a3781ba2
6
+ metadata.gz: 4cf277261fcf00a553dc1defe27a24b74ace4d34f7760e5033d5b52dbbb2fa87bacb0a36b32e8847bd3af4e2d93381caf3fa17b8f5e6fa0953fc32a3105f2b01
7
+ data.tar.gz: 6e5f421543e3a2871f1b8e068f12dab5ebec5586ff18ee297dcbca94261cca57e931c72e00af468a783170462edd333939b1e1370d715a5770b32fb3f457f4fc
data/CHANGELOG.md CHANGED
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.0] - 2026-04-30
11
+
12
+ ### Added
13
+ - `Expression#count_in(from:, to:)` returns the number of times the expression matches within an inclusive time window — useful for capacity planning and alert tuning
14
+
15
+ ## [0.3.0] - 2026-04-09
16
+
17
+ ### Added
18
+ - Named cron aliases (`@hourly`, `@daily`, `@midnight`, `@weekly`, `@monthly`, `@yearly`, `@annually`) with case-insensitive matching
19
+ - `valid?` and `validate` now accept alias expressions
20
+
10
21
  ## [0.2.0] - 2026-04-03
11
22
 
12
23
  ### Added
data/README.md CHANGED
@@ -49,6 +49,15 @@ cron.matches?(Time.new(2026, 3, 22, 9, 0, 0)) # => true
49
49
  cron.matches?(Time.new(2026, 3, 22, 10, 0, 0)) # => false
50
50
  ```
51
51
 
52
+ ### Counting Occurrences in a Window
53
+
54
+ ```ruby
55
+ cron = Philiprehberger::CronParser.new('*/15 * * * *')
56
+ from = Time.new(2026, 4, 30, 9, 0, 0)
57
+ to = Time.new(2026, 4, 30, 10, 0, 0)
58
+ cron.count_in(from: from, to: to) # => 5
59
+ ```
60
+
52
61
  ### Human-Readable Description
53
62
 
54
63
  ```ruby
@@ -69,6 +78,20 @@ Philiprehberger::CronParser.new('0 0 * * MON,WED,FRI') # specific days
69
78
  Philiprehberger::CronParser.new('0 0 * * 1,WED,5') # mixed numeric and named
70
79
  ```
71
80
 
81
+ ### Named Aliases
82
+
83
+ Standard crontab aliases are supported (case-insensitive):
84
+
85
+ ```ruby
86
+ Philiprehberger::CronParser.new('@hourly') # => 0 * * * *
87
+ Philiprehberger::CronParser.new('@daily') # => 0 0 * * *
88
+ Philiprehberger::CronParser.new('@midnight') # alias for @daily
89
+ Philiprehberger::CronParser.new('@weekly') # => 0 0 * * 0
90
+ Philiprehberger::CronParser.new('@monthly') # => 0 0 1 * *
91
+ Philiprehberger::CronParser.new('@yearly') # => 0 0 1 1 *
92
+ Philiprehberger::CronParser.new('@annually') # alias for @yearly
93
+ ```
94
+
72
95
  ### Validation
73
96
 
74
97
  ```ruby
@@ -104,6 +127,7 @@ Philiprehberger::CronParser.new('0 9 * * MON-FRI') # named weekdays
104
127
  | `Expression#next(from:)` | Calculate the next matching time |
105
128
  | `Expression#prev(from:)` | Calculate the previous matching time |
106
129
  | `Expression#next_n(n, from:)` | Calculate the next N matching times |
130
+ | `Expression#count_in(from:, to:)` | Count occurrences within an inclusive `[from, to]` window |
107
131
  | `Expression#matches?(time)` | Check if a time matches the expression |
108
132
  | `Expression#human_readable` | Human-readable description of the expression |
109
133
  | `Expression#description` | Alias for `human_readable` |
@@ -30,13 +30,27 @@ module Philiprehberger
30
30
  weekday: 'day of week'
31
31
  }.freeze
32
32
 
33
- # @return [String] the original expression
33
+ ALIASES = {
34
+ '@yearly' => '0 0 1 1 *',
35
+ '@annually' => '0 0 1 1 *',
36
+ '@monthly' => '0 0 1 * *',
37
+ '@weekly' => '0 0 * * 0',
38
+ '@daily' => '0 0 * * *',
39
+ '@midnight' => '0 0 * * *',
40
+ '@hourly' => '0 * * * *'
41
+ }.freeze
42
+
43
+ # @return [String] the original (post-alias expansion) expression
34
44
  attr_reader :expression
35
45
 
36
- # @param expr [String] a 5-field cron expression
46
+ # @param expr [String] a 5-field cron expression or named alias (e.g. "@daily")
37
47
  # @raise [Error] if the expression is invalid
38
48
  def initialize(expr)
39
- @expression = expr.strip
49
+ stripped = expr.strip
50
+ stripped = ALIASES[stripped.downcase] if stripped.start_with?('@')
51
+ raise Error, "Unknown cron alias: #{expr.strip}" if stripped.nil?
52
+
53
+ @expression = stripped
40
54
  parts = @expression.split(/\s+/)
41
55
  raise Error, "Expected 5 fields, got #{parts.size}: #{@expression}" unless parts.size == 5
42
56
 
@@ -92,6 +106,30 @@ module Philiprehberger
92
106
  results
93
107
  end
94
108
 
109
+ # Count occurrences within a time window [from, to].
110
+ #
111
+ # The window is inclusive on both ends; `from` is treated as the cursor's
112
+ # starting point so an exact match at `from` counts only if it lies on a
113
+ # minute boundary that the schedule fires on.
114
+ #
115
+ # @param from [Time] start of the window
116
+ # @param to [Time] end of the window
117
+ # @return [Integer] the count of occurrences in the window
118
+ # @raise [Error] if `to` is earlier than `from`
119
+ def count_in(from:, to:)
120
+ raise Error, '`to` must be greater than or equal to `from`' if to < from
121
+
122
+ count = 0
123
+ cursor = round_to_minute(from) - 60
124
+ loop do
125
+ cursor = self.next(from: cursor)
126
+ break if cursor > to
127
+
128
+ count += 1
129
+ end
130
+ count
131
+ end
132
+
95
133
  # Return a human-readable description of the expression
96
134
  #
97
135
  # @return [String]
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Philiprehberger
4
4
  module CronParser
5
- VERSION = '0.2.0'
5
+ VERSION = '0.4.0'
6
6
  end
7
7
  end
@@ -37,6 +37,12 @@ module Philiprehberger
37
37
  def self.validate(expr)
38
38
  errors = []
39
39
  stripped = expr.strip
40
+ if stripped.start_with?('@')
41
+ alias_expanded = Expression::ALIASES[stripped.downcase]
42
+ return { valid: false, errors: ["Unknown cron alias: #{stripped}"] } if alias_expanded.nil?
43
+
44
+ stripped = alias_expanded
45
+ end
40
46
  parts = stripped.split(/\s+/)
41
47
 
42
48
  unless parts.size == 5
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: philiprehberger-cron_parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Philip Rehberger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-04-03 00:00:00.000000000 Z
11
+ date: 2026-05-01 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Parse standard 5-field cron expressions and calculate next/previous occurrences,
14
14
  match times against patterns, and generate human-readable descriptions.