From 5cd05545af8757a104b0aa864e2ac9a7f2db3256 Mon Sep 17 00:00:00 2001 From: Craig Little Date: Mon, 11 Apr 2016 18:16:15 -0700 Subject: [PATCH] Support zero scalar for-duration calculations The first active period in the chosen direction is found and the time at the beginning of that period returned. If the specified origin is in the middle of an active period, that time is returned. The same logic is applied looking forward or backward with the only difference being the direction. Fixes #59. --- lib/biz/calculation/for_duration.rb | 25 ++++++-- spec/calculation/for_duration_spec.rb | 88 +++++++++++++++++++++++++-- 2 files changed, 105 insertions(+), 8 deletions(-) diff --git a/lib/biz/calculation/for_duration.rb b/lib/biz/calculation/for_duration.rb index 8e02941..e0c02ba 100644 --- a/lib/biz/calculation/for_duration.rb +++ b/lib/biz/calculation/for_duration.rb @@ -38,27 +38,40 @@ def unit self.class.unit end + def moment_before(time) + schedule.periods.before(time).first.end_time + end + + def moment_after(time) + schedule.periods.after(time).first.start_time + end + [ *%i[second seconds minute minutes hour hours].map { |unit| const_set( unit.to_s.capitalize, Class.new(self) do def before(time) - timeline(:before, time).last.start_time + return moment_before(time) if scalar.zero? + + advanced_periods(:before, time).last.start_time end def after(time) - timeline(:after, time).last.end_time + return moment_after(time) if scalar.zero? + + advanced_periods(:after, time).last.end_time end private - def timeline(direction, time) + def advanced_periods(direction, time) schedule .periods .public_send(direction, time) .timeline - .for(Duration.public_send(unit, scalar)).to_a + .for(Duration.public_send(unit, scalar)) + .to_a end end ) @@ -68,10 +81,14 @@ def timeline(direction, time) unit.to_s.capitalize, Class.new(self) do def before(time) + return moment_before(time) if scalar.zero? + periods(:before, time).first.end_time end def after(time) + return moment_after(time) if scalar.zero? + periods(:after, time).first.start_time end diff --git a/spec/calculation/for_duration_spec.rb b/spec/calculation/for_duration_spec.rb index d0483b1..f9210af 100644 --- a/spec/calculation/for_duration_spec.rb +++ b/spec/calculation/for_duration_spec.rb @@ -31,7 +31,9 @@ %i[second seconds].each do |unit| describe ".#{unit}" do - subject(:calculation) { described_class.send(unit, schedule, 90) } + let(:scalar) { 90 } + + subject(:calculation) { described_class.send(unit, schedule, scalar) } describe '#before' do let(:time) { Time.utc(2006, 1, 4, 16, 1, 30) } @@ -39,6 +41,15 @@ it 'returns the backward time after the elapsed duration' do expect(calculation.before(time)).to eq Time.utc(2006, 1, 4, 16) end + + context 'when the scalar is zero' do + let(:scalar) { 0 } + let(:time) { Time.utc(2006, 1, 3) } + + it 'returns the first active moment backward in time' do + expect(calculation.before(time)).to eq Time.utc(2006, 1, 2, 17) + end + end end describe '#after' do @@ -47,13 +58,24 @@ it 'returns the forward time after the elapsed duration' do expect(calculation.after(time)).to eq Time.utc(2006, 1, 4, 16) end + + context 'when the scalar is zero' do + let(:scalar) { 0 } + let(:time) { Time.utc(2006, 1, 3) } + + it 'returns the first active moment forward in time' do + expect(calculation.after(time)).to eq Time.utc(2006, 1, 3, 10) + end + end end end end %i[minute minutes].each do |unit| describe ".#{unit}" do - subject(:calculation) { described_class.send(unit, schedule, 90) } + let(:scalar) { 90 } + + subject(:calculation) { described_class.send(unit, schedule, scalar) } describe '#before' do let(:time) { Time.utc(2006, 1, 4, 16, 30) } @@ -61,6 +83,15 @@ it 'returns the backward time after the elapsed duration' do expect(calculation.before(time)).to eq Time.utc(2006, 1, 4, 15) end + + context 'when the scalar is zero' do + let(:scalar) { 0 } + let(:time) { Time.utc(2006, 1, 3) } + + it 'returns the first active moment backward in time' do + expect(calculation.before(time)).to eq Time.utc(2006, 1, 2, 17) + end + end end describe '#after' do @@ -69,13 +100,24 @@ it 'returns the forward time after the elapsed duration' do expect(calculation.after(time)).to eq Time.utc(2006, 1, 4, 17) end + + context 'when the scalar is zero' do + let(:scalar) { 0 } + let(:time) { Time.utc(2006, 1, 3) } + + it 'returns the first active moment forward in time' do + expect(calculation.after(time)).to eq Time.utc(2006, 1, 3, 10) + end + end end end end %i[hour hours].each do |unit| describe ".#{unit}" do - subject(:calculation) { described_class.send(unit, schedule, 3) } + let(:scalar) { 3 } + + subject(:calculation) { described_class.send(unit, schedule, scalar) } describe '#before' do let(:time) { Time.utc(2006, 1, 4, 17) } @@ -83,6 +125,15 @@ it 'returns the backward time after the elapsed duration' do expect(calculation.before(time)).to eq Time.utc(2006, 1, 4, 14) end + + context 'when the scalar is zero' do + let(:scalar) { 0 } + let(:time) { Time.utc(2006, 1, 3) } + + it 'returns the first active moment backward in time' do + expect(calculation.before(time)).to eq Time.utc(2006, 1, 2, 17) + end + end end describe '#after' do @@ -91,13 +142,24 @@ it 'returns the forward time after the elapsed duration' do expect(calculation.after(time)).to eq Time.utc(2006, 1, 4, 17) end + + context 'when the scalar is zero' do + let(:scalar) { 0 } + let(:time) { Time.utc(2006, 1, 3) } + + it 'returns the first active moment forward in time' do + expect(calculation.after(time)).to eq Time.utc(2006, 1, 3, 10) + end + end end end end %i[day days].each do |unit| describe ".#{unit}" do - subject(:calculation) { described_class.send(unit, schedule, 2) } + let(:scalar) { 2 } + + subject(:calculation) { described_class.send(unit, schedule, scalar) } describe '#before' do context 'when the advanced time is within a period' do @@ -133,6 +195,15 @@ ) end end + + context 'when the scalar is zero' do + let(:scalar) { 0 } + let(:time) { Time.utc(2006, 1, 2, 14) } + + it 'returns the first active moment backward in time' do + expect(calculation.before(time)).to eq Time.utc(2006, 1, 2, 14) + end + end end describe '#after' do @@ -169,6 +240,15 @@ ) end end + + context 'when the scalar is zero' do + let(:scalar) { 0 } + let(:time) { Time.utc(2006, 1, 2, 13) } + + it 'returns the first active moment forward in time' do + expect(calculation.after(time)).to eq Time.utc(2006, 1, 2, 13) + end + end end end end