class Monthly
def initialize(date = Date.today)
@date = date
end
def first
Date.civil(@date.year, @date.month, 1)
end
def last
date = first
date += 1 until date.succ.day == 1
date
end
def first_on_calendar
date = first
date -= 1 until date.wday == 0
date
end
def last_on_calendar
date = last
date += 1 until date.wday == 6
date
end
def calendar_dates
dates = []
first_on_calendar.upto(last_on_calendar) {|d| dates << d}
dates
end
end
require 'timecop'
require 'monthly'
describe Monthly do
describe 'April 2009' do
before do
Timecop.freeze(Date.civil(2009, 4, 16)) do
@monthly = Monthly.new
end
end
it 'should give april 1 for the first day' do
@monthly.first.should == Date.civil(2009, 4, 1)
end
it 'should give april 30 for the last day' do
@monthly.last.should == Date.civil(2009, 4, 30)
end
it 'should give march 29 for the first day on calendar' do
@monthly.first_on_calendar.should == Date.civil(2009, 3, 29)
end
it 'should give may 2 for the last day on calendar' do
@monthly.last_on_calendar.should == Date.civil(2009, 5, 2)
end
it 'should give the correct set of dates' do
dates = %w(
29 30 31 1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 1 2
).map {|s| s.to_i}
@monthly.calendar_dates.map {|d| d.day}.should == dates
end
end
describe 'October 2008' do
before do
@monthly = Monthly.new(Date.civil(2008, 10, 20))
end
it 'should give october 1 for the first day' do
@monthly.first.should == Date.civil(2008, 10, 1)
end
it 'should give october 31 for the last day' do
@monthly.last.should == Date.civil(2008, 10, 31)
end
it 'should give september 28 for the first day on calendar' do
@monthly.first_on_calendar.should == Date.civil(2008, 9, 28)
end
it 'should give november 1 for the last day on calendar' do
@monthly.last_on_calendar.should == Date.civil(2008, 11, 1)
end
it 'should give the correct set of dates' do
dates = %w(
28 29 30 1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31 1
).map {|s| s.to_i}
@monthly.calendar_dates.map {|d| d.day}.should == dates
end
end
end
Refactorings
No refactoring yet !
roman
April 16, 2009, April 16, 2009 18:56, permalink
Here you have a version that does not have loops for getting the last day of the month, first and last day of the week... it uses metadata for the month length, I thought there was some of this info on the Date class, but I didn't find any in the documentation... at least this is a good start I think :-).
class Monthly
DAYS_OF_MONTH = [
31,
[28, 29],
31,
30,
31,
30,
31,
31,
30,
31,
30,
31
]
def initialize(date = Date.today)
@date = date
end
def first
Date.civil(@date.year, @date.month, 1)
end
def last
date = first
date += (days_of_month - 1)
date
end
def first_on_calendar
date = first
date -= date.wday # this will result in 0
date
end
def last_on_calendar
date = last
date += (6 - date.wday)
date
end
def calendar_dates
dates = []
first_on_calendar.upto(last_on_calendar) {|d| dates << d}
dates
end
private
def days_of_month
month_days = DAYS_OF_MONTH[@date.month - 1] # 0 based index
if month_days.kind_of?(Array)
month_days[(@date.leap? ? 1 : 0)]
else
month_days
end
end
end
Ben Atkin
April 17, 2009, April 17, 2009 05:59, permalink
Thanks to roman's solution, it just popped into my head how to get rid of the loops. Since I had to make a special case involving the end of the year, I added an additional example to my spec.
class Monthly
attr_reader :date
def initialize(d = Date.today)
@date = d
end
def first
Date.civil(date.year, date.month, 1)
end
def last
date.month == 12 ? Date.civil(date.year, 12, 31) : Date.civil(date.year, date.month + 1, 1) - 1
end
def first_on_calendar
first - first.wday
end
def last_on_calendar
last + 6 - last.wday
end
def calendar_dates
first_on_calendar..last_on_calendar
end
end
require 'timecop'
require 'monthly'
describe Monthly do
describe 'April 2009' do
before do
Timecop.freeze(Date.civil(2009, 4, 16)) do
@monthly = Monthly.new
end
end
it 'should give april 1 for the first day' do
@monthly.first.should == Date.civil(2009, 4, 1)
end
it 'should give april 30 for the last day' do
@monthly.last.should == Date.civil(2009, 4, 30)
end
it 'should give march 29 for the first day on calendar' do
@monthly.first_on_calendar.should == Date.civil(2009, 3, 29)
end
it 'should give may 2 for the last day on calendar' do
@monthly.last_on_calendar.should == Date.civil(2009, 5, 2)
end
it 'should give the correct set of dates' do
dates = %w(
29 30 31 1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 1 2
).map {|s| s.to_i}
@monthly.calendar_dates.map {|d| d.day}.should == dates
end
end
describe 'October 2008' do
before do
@monthly = Monthly.new(Date.civil(2008, 10, 20))
end
it 'should give october 1 for the first day' do
@monthly.first.should == Date.civil(2008, 10, 1)
end
it 'should give october 31 for the last day' do
@monthly.last.should == Date.civil(2008, 10, 31)
end
it 'should give september 28 for the first day on calendar' do
@monthly.first_on_calendar.should == Date.civil(2008, 9, 28)
end
it 'should give november 1 for the last day on calendar' do
@monthly.last_on_calendar.should == Date.civil(2008, 11, 1)
end
it 'should give the correct set of dates' do
dates = %w(
28 29 30 1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31 1
).map {|s| s.to_i}
@monthly.calendar_dates.map {|d| d.day}.should == dates
end
end
describe 'December 2009' do
before do
Timecop.freeze(Date.civil(2009, 12, 31)) do
@monthly = Monthly.new
end
end
it 'should give december 1 for the first day' do
@monthly.first.should == Date.civil(2009, 12, 1)
end
it 'should give december 31 for the last day' do
@monthly.last.should == Date.civil(2009, 12, 31)
end
it 'should give november 29 for the first day on calendar' do
@monthly.first_on_calendar.should == Date.civil(2009, 11, 29)
end
it 'should give january 2 for the last day on calendar' do
@monthly.last_on_calendar.should == Date.civil(2010, 1, 2)
end
it 'should give the correct set of dates' do
dates = %w(
29 30 1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31 1 2
).map {|s| s.to_i}
@monthly.calendar_dates.map {|d| d.day}.should == dates
end
end
end
steenslag
June 25, 2009, June 25, 2009 21:42, permalink
require 'date' def days_to_show(year, month, num_week_rows = 5) first_of_month = Date.new(year, month, 1) first = first_of_month - first_of_month.cwday #+ 1 # +1 for calendar starting on monday, remove for starting on sunday last = first + num_week_rows * 7 (first...last).to_a end
I am reasonably happy with this code, but wish it was a bit simpler and more declarative. Anyone know of a simple way to get the last day of the month, or the last Sunday, or the next Saturday without resulting to while loops?