I needed the possibility to display any given month of a year, along with some additional informations in Rails. I wanted to place the additional informations within the calendar in a way that no available plugin allowed me to, so I rolled my own.
In this blog post I want to share my approach to build a simple calendar helper for Ruby. The following code does not use any functionality provided by Rails, so you can use it anywhere you want.
1. some thoughts on the first row to display
I need my calendar to always start at Monday. The first day of a given month rarely is a Monday so you’ll need to display some days of the prior month as well.
The days that need to be displayed prior to the first day in a month can be calculated if you enumerate each day of the week; e.g. Sunday equals 0, Monday 1, Tuesday 2, and so forth. That’s just what Ruby does.
Now, all you need to do is to determine which day of the week the first of a given month is. This will give you enough informations to calculate how many days need to be displayed prior:
require 'date'
year = 2012
month = 7
first_day_of_month = Date.new(year, month, 1)
days_to_display_prior = (first_day_of_month.sunday? ? -6 : (first_day_of_month.wday > 1 ? -first_day_of_month.wday : 1))
Now we can determine the date of the first monday in the first row
first_monday_in_calendar = Date.new(year, month - 1, days_to_display_prior)
# => yields 2012-06-25
and calculate the remaining row as well:
7.times.map { |wday| first_monday_in_calendar.next_day(wday) }
2. calculate how many rows to display
In most cases you’ll need to display between four and six rows, depending on the number of days in a month as well as the weekday on which a given month starts. So let’s calculate that:
last_day_in_month = first_day_of_month.next_month.prev_day
# => yields 2012-07-31
days_in_month = last_day_in_month.yday - first_day_of_month.yday
# => yields 30
days_to_display = days_in_month + days_to_display_prior.abs
# => yields 36
weeks_to_display = weeks_to_display = (days_to_display.to_f / 7).ceil
# => yields 6
3. calculate all dates to be displayed
weeks_to_display.times.map { |week|
7.times.map { |wday|
first_monday_in_calendar.next_day(week * 7 + wday)
}
}
That’s it. Now you can wrap this logic into a presenter or extended it.
You can find a complete gist of the above source here.