I am using the ice_cube
gem to create schedules. Making a monthly schedule starting on the 31st misses all months with fewer than 31 days. I'd like to schedule the last day of the month on those months. If my schedule starts on the 30th I want the 30th of every month and the last day of February. Leap years complicate the matter further.
What's a good way to create schedules that handles starting on the 29th, 30th or 31st?
This passes all my specs, but is fugly and probably breaks for schedules longer than a year (which I don't care about yet).
class LeasePaymentSchedule
def self.monthly(a bunch of args)
case start_day
when 31
schedule = IceCube::Schedule.new(start, scheduler_options) do |s|
s.add_recurrence_rule IceCube::Rule.monthly.day_of_month(-1).until(end_time)
end
when 30,29
schedule = IceCube::Schedule.new(start, scheduler_options) do |s|
s.add_recurrence_rule IceCube::Rule.monthly.day_of_month(start_day).until(end_time)
end
schedule.all_occurrences.each do |o|
next unless [1,3,6,8,10].include? o.month
missed = (o + 1.month).yday
# Probably breaks for durations longer than 1 year
schedule.add_recurrence_rule IceCube::Rule.yearly.day_of_year(missed).count(1)
end
else
schedule = IceCube::Schedule.new(start, scheduler_options) do |s|
s.add_recurrence_rule IceCube::Rule.monthly.day_of_month(start_day).until(end_time)
end
end
schedule
end
end
So many specs:
Finished in 4.17 seconds
390 examples, 0 failures
-
shared_examples_for :a_schedule do
it 'returns an IceCube Schedule' do
schedule.should be_a IceCube::Schedule
end
it 'should start on the correct day' do
schedule.start_time.should eq expected_start
end
it 'has the right number of occurrences' do
schedule.all_occurrences.size.should eq expected_occurrences
end
end
describe :monthly do
let(:expected_occurrences) { 12 }
let(:expected_start) { date.next_month.beginning_of_day }
let(:schedule) { LeasePaymentSchedule.monthly }
before do
Date.stub(:today).and_return(date)
end
shared_examples_for :on_the_28th do
let(:date) { Time.parse "#{year}-#{month}-28" }
it_behaves_like :a_schedule
end
shared_examples_for :on_the_29th do
let(:date) { Time.parse "#{year}-#{month}-29" }
it_behaves_like :on_the_28th
it_behaves_like :a_schedule
end
shared_examples_for :on_the_30th do
let(:date) { Time.parse "#{year}-#{month}-30" }
it_behaves_like :on_the_29th
it_behaves_like :a_schedule
end
shared_examples_for :on_the_31st do
let(:date) { Time.parse "#{year}-#{month}-31" }
it_behaves_like :on_the_30th
it_behaves_like :a_schedule
end
shared_examples_for :the_whole_year do
context :february do
let(:month) { 2 }
it_behaves_like :on_the_28th
end
[ 4, 7, 9, 11 ].each do |month_num|
let(:month) { month_num }
it_behaves_like :on_the_30th
end
[ 1, 3, 5, 6, 8, 10, 12].each do |month_num|
let(:month) { month_num }
it_behaves_like :on_the_31st
end
end
context :a_leap_year do
let(:year) { 2012 }
context :february_29th do
let(:month) { 2 }
it_behaves_like :on_the_29th
end
it_behaves_like :the_whole_year
end
context :before_a_leap_year do
let(:year) { 2011 }
it_behaves_like :the_whole_year
end
context :nowhere_near_a_leap_year do
let(:year) { 2010 }
it_behaves_like :the_whole_year
end
end