sof-cycle 0.1.12 → 0.1.13
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 +4 -4
- data/CHANGELOG.md +13 -5
- data/README.md +3 -1
- data/Rakefile +1 -0
- data/checksums/sof-cycle-0.1.12.gem.sha512 +1 -0
- data/lib/sof/cycle/version.rb +1 -1
- data/lib/sof/cycle.rb +6 -3
- data/lib/sof/cycles/end_of.rb +9 -10
- data/lib/sof/cycles/interval.rb +75 -0
- data/lib/sof/cycles/within.rb +2 -0
- data/lib/sof/parser.rb +4 -2
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0eb9458907426d68d8fc05c159a9bb782819b2ec821baf2ec24109b45c6794ff
|
|
4
|
+
data.tar.gz: e4c0b55074d59aad3f4e562e579759d8d85413b1fd93c93c873b79bdbff1c629
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c799aed72bf8973a8b6438cab7d2635f2563575fcf3d16eac4b4bf38df409953000d78afc8b691ed4b5e4eb40b39a7969858e79d9ee844697ce113e1f0170ae5
|
|
7
|
+
data.tar.gz: c99b76559985729d0ea59ee3ff5be523500965578c450c85a90c24fd2d958ebd6eb4ff4ad550d101e8178423f2f39d02ffe79a47a28f716e34865c4028ce83a5
|
data/CHANGELOG.md
CHANGED
|
@@ -5,14 +5,22 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [0.1.
|
|
8
|
+
## [0.1.13] - 2026-03-20
|
|
9
9
|
|
|
10
10
|
### Added
|
|
11
11
|
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
## [0.1.11] - 2025-09-05
|
|
12
|
+
- Interval cycle kind (`I` notation) — repeating windows anchored to a from_date that re-anchor from completion date (e.g., `V1I24MF2026-03-31`)
|
|
15
13
|
|
|
16
14
|
### Changed
|
|
17
15
|
|
|
18
|
-
-
|
|
16
|
+
- Dormant capability is now declared on each cycle class (`def self.dormant_capable? = true`) instead of only in `Parser.dormant_capable_kinds`
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- EndOf cycle `final_date` was off by one period — `V1E12M` now correctly expires at the end of the 12th month, not the 11th
|
|
21
|
+
|
|
22
|
+
## [0.1.12] - 2025-09-05
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- Missing code coverage and updated EndOf to properly handle dormant cycles.
|
data/README.md
CHANGED
|
@@ -25,7 +25,9 @@ cycle.to_s # => "3x in the prior 3 days"
|
|
|
25
25
|
|
|
26
26
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
27
27
|
|
|
28
|
-
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
28
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
29
|
+
|
|
30
|
+
This project is managed with [Reissue](https://github.com/SOFware/reissue). Releases are automated via the [shared release workflow](https://github.com/SOFware/reissue/blob/main/.github/workflows/SHARED_WORKFLOW_README.md). Trigger a release by running the "Release gem to RubyGems.org" workflow from the Actions tab.
|
|
29
31
|
|
|
30
32
|
## Contributing
|
|
31
33
|
|
data/Rakefile
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
e7091d8a602dd216e947b2c0207ad66383b0322646c1b3ab4eacd670cd885035d3bf6e1e5ba3c848466aa9989f298ef8993fd1a260559953bb47a866cc613a52
|
data/lib/sof/cycle/version.rb
CHANGED
data/lib/sof/cycle.rb
CHANGED
|
@@ -130,6 +130,8 @@ module SOF
|
|
|
130
130
|
attr_reader :notation_id, :kind, :valid_periods
|
|
131
131
|
def volume_only? = @volume_only
|
|
132
132
|
|
|
133
|
+
def dormant_capable? = false
|
|
134
|
+
|
|
133
135
|
def recurring? = raise "#{name} must implement #{__method__}"
|
|
134
136
|
|
|
135
137
|
# Raises an error if the given period isn't in the list of valid periods.
|
|
@@ -215,12 +217,13 @@ module SOF
|
|
|
215
217
|
|
|
216
218
|
attr_reader :parser
|
|
217
219
|
|
|
218
|
-
delegate [:activated_notation, :volume, :from,
|
|
220
|
+
delegate [:activated_notation, :volume, :from,
|
|
221
|
+
:from_date, :time_span, :period,
|
|
219
222
|
:humanized_period, :period_key, :active?] => :@parser
|
|
220
223
|
delegate [:kind, :recurring?, :volume_only?, :valid_periods] => "self.class"
|
|
221
224
|
delegate [:period_count, :duration] => :time_span
|
|
222
|
-
delegate [:calendar?, :dormant?, :end_of?, :
|
|
223
|
-
:within?] => :kind_inquiry
|
|
225
|
+
delegate [:calendar?, :dormant?, :end_of?, :interval?, :lookback?,
|
|
226
|
+
:volume_only?, :within?] => :kind_inquiry
|
|
224
227
|
|
|
225
228
|
def kind_inquiry = ActiveSupport::StringInquirer.new(kind.to_s)
|
|
226
229
|
|
data/lib/sof/cycles/end_of.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Captures the logic for enforcing the EndOf cycle variant
|
|
4
4
|
# E.g. "V1E18MF2020-01-05" means:
|
|
5
|
-
# You're good until the end of the
|
|
5
|
+
# You're good until the end of the 18th month from 2020-01-05.
|
|
6
6
|
# Complete 1 by that date to reset the cycle.
|
|
7
7
|
#
|
|
8
8
|
# Some of the calculations are quite different from other cycles.
|
|
@@ -18,6 +18,8 @@ module SOF
|
|
|
18
18
|
|
|
19
19
|
def self.recurring? = true
|
|
20
20
|
|
|
21
|
+
def self.dormant_capable? = true
|
|
22
|
+
|
|
21
23
|
def self.description
|
|
22
24
|
"End of - occurrences by the end of a time period"
|
|
23
25
|
end
|
|
@@ -62,15 +64,15 @@ module SOF
|
|
|
62
64
|
#
|
|
63
65
|
# @param [nil] _ Unused parameter, maintained for compatibility
|
|
64
66
|
# @return [Date] The final date of the cycle calculated as the end of the
|
|
65
|
-
# nth
|
|
67
|
+
# nth period after the FROM date
|
|
66
68
|
#
|
|
67
69
|
# @example
|
|
68
70
|
# Cycle.for("V1E18MF2020-01-09").final_date
|
|
69
|
-
# # => #<Date: 2021-
|
|
71
|
+
# # => #<Date: 2021-07-31>
|
|
70
72
|
def final_date(_ = nil)
|
|
71
73
|
return nil if parser.dormant? || from_date.nil?
|
|
72
74
|
time_span
|
|
73
|
-
.end_date(start_date
|
|
75
|
+
.end_date(start_date)
|
|
74
76
|
.end_of_month
|
|
75
77
|
end
|
|
76
78
|
|
|
@@ -79,14 +81,11 @@ module SOF
|
|
|
79
81
|
private
|
|
80
82
|
|
|
81
83
|
def dormant_to_s
|
|
82
|
-
|
|
83
|
-
#{volume}x by the last day of the #{subsequent_ordinal}
|
|
84
|
-
subsequent #{period}
|
|
85
|
-
DESC
|
|
84
|
+
"#{volume}x by the last day of the #{ordinalized_period_count} #{period}"
|
|
86
85
|
end
|
|
87
86
|
|
|
88
|
-
def
|
|
89
|
-
ActiveSupport::Inflector.ordinalize(period_count
|
|
87
|
+
def ordinalized_period_count
|
|
88
|
+
ActiveSupport::Inflector.ordinalize(period_count)
|
|
90
89
|
end
|
|
91
90
|
end
|
|
92
91
|
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Captures the logic for enforcing the Interval cycle variant
|
|
4
|
+
# E.g. "V1I24MF2026-03-31" means:
|
|
5
|
+
# Complete 1 every 24 months, current window from 2026-03-31.
|
|
6
|
+
# After completion, the consuming app re-anchors from the completion date.
|
|
7
|
+
#
|
|
8
|
+
# Unlike EndOf, there is no end-of-month rounding.
|
|
9
|
+
# Unlike Lookback, the window is anchored to a from_date, not sliding from today.
|
|
10
|
+
# Unlike Within, the window is repeating — it re-anchors from the completion date.
|
|
11
|
+
module SOF
|
|
12
|
+
module Cycles
|
|
13
|
+
class Interval < Cycle
|
|
14
|
+
@volume_only = false
|
|
15
|
+
@notation_id = "I"
|
|
16
|
+
@kind = :interval
|
|
17
|
+
@valid_periods = %w[D W M Y]
|
|
18
|
+
|
|
19
|
+
def self.recurring? = true
|
|
20
|
+
|
|
21
|
+
def self.dormant_capable? = true
|
|
22
|
+
|
|
23
|
+
def self.description
|
|
24
|
+
"Interval - occurrences within a repeating window that re-anchors from completion date"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.examples
|
|
28
|
+
["V1I24MF2026-03-31 - once every 24 months from March 31, 2026 (re-anchors after completion)"]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def to_s
|
|
32
|
+
return dormant_to_s unless active?
|
|
33
|
+
|
|
34
|
+
"#{volume}x every #{humanized_span} from #{start_date.to_fs(:american)}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Returns the expiration date for the current window
|
|
38
|
+
#
|
|
39
|
+
# @return [Date, nil] The final date of the current window
|
|
40
|
+
def expiration_of(_ = nil, anchor: nil)
|
|
41
|
+
final_date
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Is the supplied anchor date within the current window?
|
|
45
|
+
#
|
|
46
|
+
# @return [Boolean] true if the anchor is before or on the final date
|
|
47
|
+
def satisfied_by?(_ = nil, anchor: Date.current)
|
|
48
|
+
anchor <= final_date
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Returns the from_date as the last completed date
|
|
52
|
+
def last_completed(_ = nil) = from_date&.to_date
|
|
53
|
+
|
|
54
|
+
# Calculates the final date of the current window
|
|
55
|
+
#
|
|
56
|
+
# @return [Date] from_date + period (no end-of-month rounding)
|
|
57
|
+
#
|
|
58
|
+
# @example
|
|
59
|
+
# Cycle.for("V1I24MF2026-03-31").final_date
|
|
60
|
+
# # => #<Date: 2028-03-31>
|
|
61
|
+
def final_date(_ = nil)
|
|
62
|
+
return nil if start_date.nil?
|
|
63
|
+
time_span.end_date(start_date)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def start_date(_ = nil) = from_date&.to_date
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def dormant_to_s
|
|
71
|
+
"#{volume}x every #{humanized_span}"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
data/lib/sof/cycles/within.rb
CHANGED
data/lib/sof/parser.rb
CHANGED
|
@@ -14,13 +14,15 @@ module SOF
|
|
|
14
14
|
extend Forwardable
|
|
15
15
|
PARTS_REGEX = /
|
|
16
16
|
^(?<vol>V(?<volume>\d*))? # optional volume
|
|
17
|
-
(?<set>(?<kind>L|C|W|E) # kind
|
|
17
|
+
(?<set>(?<kind>L|C|W|E|I) # kind
|
|
18
18
|
(?<period_count>\d+) # period count
|
|
19
19
|
(?<period_key>D|W|M|Q|Y)?)? # period_key
|
|
20
20
|
(?<from>F(?<from_date>\d{4}-\d{2}-\d{2}))?$ # optional from
|
|
21
21
|
/ix
|
|
22
22
|
|
|
23
|
-
def self.dormant_capable_kinds
|
|
23
|
+
def self.dormant_capable_kinds
|
|
24
|
+
Cycle.cycle_handlers.select(&:dormant_capable?).map(&:notation_id).compact
|
|
25
|
+
end
|
|
24
26
|
|
|
25
27
|
def self.for(notation_or_parser)
|
|
26
28
|
return notation_or_parser if notation_or_parser.is_a? self
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sof-cycle
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.13
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jim Gay
|
|
@@ -55,6 +55,7 @@ files:
|
|
|
55
55
|
- checksums/sof-cycle-0.1.1.gem.sha512
|
|
56
56
|
- checksums/sof-cycle-0.1.10.gem.sha512
|
|
57
57
|
- checksums/sof-cycle-0.1.11.gem.sha512
|
|
58
|
+
- checksums/sof-cycle-0.1.12.gem.sha512
|
|
58
59
|
- checksums/sof-cycle-0.1.2.gem.sha512
|
|
59
60
|
- checksums/sof-cycle-0.1.6.gem.sha512
|
|
60
61
|
- checksums/sof-cycle-0.1.7.gem.sha512
|
|
@@ -66,6 +67,7 @@ files:
|
|
|
66
67
|
- lib/sof/cycles/calendar.rb
|
|
67
68
|
- lib/sof/cycles/dormant.rb
|
|
68
69
|
- lib/sof/cycles/end_of.rb
|
|
70
|
+
- lib/sof/cycles/interval.rb
|
|
69
71
|
- lib/sof/cycles/lookback.rb
|
|
70
72
|
- lib/sof/cycles/volume_only.rb
|
|
71
73
|
- lib/sof/cycles/within.rb
|