Skip to content

Commit

Permalink
Merge pull request #15 from SOFware/fix/end-of-expires-after
Browse files Browse the repository at this point in the history
Fix Cycles::EndOf
  • Loading branch information
jdowd authored Sep 1, 2024
2 parents abfbb64 + f5e1fb6 commit dc0887f
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 47 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [0.1.3] - Unreleased

### Fixed

- `Cycles::EndOf` to have the correct behavior

## [0.1.2] - 2024-08-09

### Added
Expand Down
36 changes: 35 additions & 1 deletion lib/sof/cycles/end_of.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# frozen_string_literal: true

# Captures the logic for enforcing the EndOf cycle variant
# E.g. "V1E18MF2020-01-05" means:
# You're good until the end of the 17th subsequent month from 2020-01-05.
# Complete 1 by that date to reset the cycle.
#
# Some of the calculations are quite different from other cycles.
# Whereas other cycles look at completion dates to determine if the cycle is
# satisfied, this cycle checks whether the anchor date is prior to the final date.
module SOF
module Cycles
class EndOf < Cycle
Expand All @@ -16,8 +24,34 @@ def to_s
"#{volume}x by #{final_date.to_fs(:american)}"
end

# Returns the expiration date for the cycle
#
# @param [nil] _ Unused parameter, maintained for compatibility
# @param anchor [nil] _ Unused parameter, maintained for compatibility
# @return [Date] The final date of the cycle
#
# @example
# Cycle.for("V1E18MF2020-01-09")
# .expiration_of(anchor: "2020-06-04".to_date)
# # => #<Date: 2021-06-30>
def expiration_of(_ = nil, anchor: nil) = final_date

# Is the supplied anchor date prior to the final date?
#
# @return [Boolean] true if the cycle is satisfied, false otherwise
def satisfied_by?(_ = nil, anchor: Date.current) = anchor <= final_date

# Calculates the final date of the cycle
#
# @param [nil] _ Unused parameter, maintained for compatibility
# @return [Date] The final date of the cycle calculated as the end of the
# nth subsequent period after the FROM date, where n = (period count - 1)
#
# @example
# Cycle.for("V1E18MF2020-01-09").final_date
# # => #<Date: 2021-06-30>
def final_date(_ = nil) = time_span
.end_date(start_date)
.end_date(start_date - 1.send(period))
.end_of_month

def start_date(_ = nil) = from_date.to_date
Expand Down
93 changes: 47 additions & 46 deletions spec/sof/cycles/end_of_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,10 @@ module SOF
let(:notation) { "V2E18MF#{from_date}" }
let(:anchor) { nil }

let(:end_date) { (from_date.to_date + 18.months).end_of_month }
let(:end_date) { (from_date.to_date + 17.months).end_of_month }
let(:from_date) { "2020-01-01" }

let(:completed_dates) do
[
recent_date,
middle_date,
early_date,
early_date,
too_early_date,
too_late_date
]
end
let(:recent_date) { from_date.to_date + 17.months }
let(:middle_date) { from_date.to_date + 2.months }
let(:early_date) { from_date.to_date + 1.month }
let(:too_early_date) { from_date.to_date - 1.day }
let(:too_late_date) { end_date + 1.day }
let(:completed_dates) { [] }

it_behaves_like "#kind returns", :end_of
it_behaves_like "#valid_periods are", %w[W M Q Y]
Expand All @@ -38,7 +24,7 @@ module SOF
end
end

@end_date = ("2020-01-01".to_date + 18.months).end_of_month
@end_date = ("2020-01-01".to_date + 17.months).end_of_month
it_behaves_like "#to_s returns",
"2x by #{@end_date.to_fs(:american)}"

Expand All @@ -52,9 +38,26 @@ module SOF
it_behaves_like "#notation returns the notation"
it_behaves_like "#as_json returns the notation"
it_behaves_like "it computes #final_date(given)",
given: nil, returns: ("2020-01-01".to_date + 18.months).end_of_month
given: nil, returns: ("2020-01-01".to_date + 17.months).end_of_month

describe "#covered_dates" do
let(:completed_dates) do
[
recent_date,
middle_date,
early_date,
early_date,
too_early_date,
too_late_date
]
end
let(:recent_date) { from_date.to_date + 17.months }
let(:middle_date) { from_date.to_date + 2.months }
let(:early_date) { from_date.to_date + 1.month }
let(:too_early_date) { from_date.to_date - 1.day }
let(:too_late_date) { end_date + 1.day }

let(:anchor) { "2021-06-29".to_date }
it "given an anchor date, returns dates that fall within it's window" do
expect(cycle.covered_dates(completed_dates, anchor:)).to eq([
recent_date,
Expand All @@ -65,56 +68,54 @@ module SOF
end
end

describe "#satisfied_by?(completed_dates, anchor:)" do
context "when the completions--judged from the <from_date>--satisfy the cycle" do
describe "#satisfied_by?(anchor:)" do
context "when the anchor date is < the final date" do
let(:anchor) { "2021-06-29".to_date }

it "returns true" do
expect(cycle).to be_satisfied_by(completed_dates, anchor:)
expect(cycle).to be_satisfied_by(anchor:)
end
end

context "when the completions are irrelevant to the given from_date" do
let(:completed_dates) do
[
10.years.ago,
Date.current,
10.years.from_now
]
end
context "when the anchor date is = the final date" do
let(:anchor) { "2021-06-30".to_date }

it "returns false" do
expect(cycle).not_to be_satisfied_by(completed_dates, anchor:)
it "returns true" do
expect(cycle).to be_satisfied_by(anchor:)
end
end

context "when the completions currently do not satisfy the cycle" do
let(:notation) { "V5E18M" }
context "when the anchor date is > the final date" do
let(:anchor) { "2021-07-01".to_date }

it "returns false" do
expect(cycle).not_to be_satisfied_by(completed_dates, anchor:)
end
end
end

context "when there are no completions" do
let(:completed_dates) { [] }
describe "#expiration_of(completion_dates)" do
context "when the anchor date is < the final date" do
let(:anchor) { "2021-06-29".to_date }

it "returns false" do
expect(cycle).not_to be_satisfied_by(completed_dates, anchor:)
it "returns the final date" do
expect(cycle.expiration_of(anchor:)).to eq "2021-06-30".to_date
end
end
end

describe "#expiration_of(completion_dates)" do
context "when the completions currently satisfy the cycle" do
it "returns the date on which the completions will no longer satisfy the cycle" do
expect(cycle.expiration_of(completed_dates)).to be nil
context "when the anchor date = the final date" do
let(:anchor) { "2021-06-30".to_date }

it "returns the final date" do
expect(cycle.expiration_of(anchor:)).to eq "2021-06-30".to_date
end
end

context "when the completions currently do not satisfy the cycle" do
let(:notation) { "V5E18M" }
context "when the anchor date > the final date" do
let(:anchor) { "2021-07-31".to_date }

it "returns nil" do
expect(cycle.expiration_of(completed_dates)).to be_nil
it "returns the final date" do
expect(cycle.expiration_of(anchor:)).to eq "2021-06-30".to_date
end
end
end
Expand Down

0 comments on commit dc0887f

Please sign in to comment.