diff --git a/lib/sof/cycles/end_of.rb b/lib/sof/cycles/end_of.rb index 3a80777..8a7b263 100644 --- a/lib/sof/cycles/end_of.rb +++ b/lib/sof/cycles/end_of.rb @@ -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 @@ -16,6 +24,32 @@ 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) + # # => # + 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 + # # => # def final_date(_ = nil) = time_span .end_date(start_date - 1.send(period)) .end_of_month diff --git a/spec/sof/cycles/end_of_spec.rb b/spec/sof/cycles/end_of_spec.rb index 11e289a..24bcbda 100644 --- a/spec/sof/cycles/end_of_spec.rb +++ b/spec/sof/cycles/end_of_spec.rb @@ -13,21 +13,7 @@ module SOF 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] @@ -55,6 +41,23 @@ module SOF 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, @@ -65,56 +68,54 @@ module SOF end end - describe "#satisfied_by?(completed_dates, anchor:)" do - context "when the completions--judged from the --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