simple_calendar yields a date per day cell and filters events for you
A wonderfully simple calendar gem for Rails
At a glance
- What is it?
- Simple Calendar is a Rails gem that renders calendars of any length from a day view to a six week grid, with no ORM dependency. It gives you a block per day cell, and if you hand it a collection of events it groups them by day before yielding, so the view code stays short.
- Who is it for?
- Adopt simple_calendar when your Rails views need a calendar grid and you already hold the events as a collection, because the gem does the per day grouping and the month padding maths and stays out of your models. Do not adopt it expecting it to query for you, since events are filtered to the visible range by whatever you put in the controller, and do not skip scoping that query or you will load every record you own.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 10 days ago.
- What is it written in?
- Mainly Ruby, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Three methods over one block that yields dates
The gem's premise is narrow and stated plainly: it is designed to do one thing really really well, render a calendar. Everything follows from a block that receives a date.
A month grid comes from month_calendar, a week grid from week_calendar with an optional number_of_weeks defaulting to 1, and an arbitrary length view from calendar with number_of_days, also defaulting to 4. That last one is what covers the day view and the four day agenda mentioned in the README.
<%= month_calendar do |date| %>
<%= date %>
<% end %>
<%= week_calendar(number_of_weeks: 2) do |date| %>
<%= date %>
<% end %>
<%= calendar(number_of_days: 4) do |date| %>
<%= date %>
<% end %>The first parameter is a symbol naming the params key that holds the current date, and :start_date is the default. If the parameter is absent the gem falls back to the current date, which is why a calendar works on first load with no query string.
Output is a full date object, so <%= date %> prints a date while <%= date.day %> prints the day of the month if that is what you want in the cell.
No ORM is involved. The README states you are free to use it with ActiveRecord, Mongoid, another ORM, or plain Ruby objects, and that decision shapes everything about event rendering.
Events are grouped by day, and only if your objects respond to the attribute
Passing the events option is what turns a grid into a calendar. The view block then receives two arguments, the date and the meetings for it:
<%= month_calendar(events: @meetings) do |date, meetings| %>
<%= date %>
<% meetings.each do |meeting| %>
<div>
<%= meeting.name %>
</div>
<% end %>
<% end %>The grouping happens in the gem, so you never write the day comparison yourself. Attribute names default to start_time, and end_time is used for multi-day events when supplied.
The documented failure mode here is the one to remember. If the objects you pass do not respond to the attribute method the gem asks for, every object is yielded on every day. The README presents that as a feature, since it lets you do your own filtering, but from the outside it looks identical to a bug: meetings repeated in all six weeks of the grid.
When the date lives on an associated model, the README's advice is to alias it rather than pass another option, by defining a start_time method on your model that delegates to the association. That keeps every existing calendar call unchanged.
You scope the query, the gem only groups what you give it
The controller example is where most of the real work sits. The README is explicit that you should query only the meetings that appear inside the current view, and it shows the range arithmetic doing that:
def index
# Scope your query to the dates being shown:
start_date = params.fetch(:start_date, Date.today).to_date
@meetings = Meeting.where(starts_at: start_date.beginning_of_month.beginning_of_week..start_date.end_of_month.end_of_week)
endThree details are load bearing. params.fetch with Date.today as the default mirrors what the view helper does, so the range matches the grid the user will see. beginning_of_month.beginning_of_week and end_of_month.end_of_week widen the range to whole weeks, because a month grid draws the leading and trailing days from the adjacent months. And the range is expressed with two dots, an inclusive range, since end_of_week lands on a day that contains events.
If you skip this and load every record, the gem will faithfully group all of them, including events that are not on screen. That is the failure mode worth designing against.
The scaffolding step assumes a conventional model: rails g scaffold Meeting name start_time:datetime for single day events, or with end_time:datetime added for multi-day ones.
# single day events
$ rails g scaffold Meeting name start_time:datetime
# multi-day events
$ rails g scaffold Meeting name start_time:datetime end_time:datetimePass attribute: :starts_at or attribute: :start_date with end_attribute: :end_date when your columns are named differently.
Views are generated into your app, so you own them afterwards
There is a generator, and it is the mechanism that keeps the gem adaptable to your markup:
$ rails g simple_calendar:viewsThat writes a folder named simple_calendar under app/views, and from then on the gem renders your partials rather than its own. Expect to edit them once, for your CSS framework's table and grid classes, and expect that you then maintain them across gem upgrades rather than inheriting improvements automatically.
Two styling shortcuts are documented. On Bootstrap the calendar already has a border and spacing for days, so nothing needs adding. Otherwise the default stylesheet can be pulled in from application.css with *= require simple_calendar, or from application.scss with @import "simple_calendar".
Two more options shape output rather than style: partial: 'products/calendar' points at a partial of your own for a specific calendar, and start_date_param: :my_date renames the params key, which matters when more than one calendar appears on a page and each needs its own navigation state.
Time zone handling is a before_action away
Day boundaries in a calendar are entirely a time zone question, and the README's guidance is that setting Time.zone makes the calendar start days compute correctly in the right zone.
That can be done globally in application.rb, or per request. If you have a User model carrying a time_zone attribute, the README suggests a before_action that sets it on every request, which is the right shape for an application with users in more than one zone.
The reason this matters more here than elsewhere is structural. The grid is a table of day cells derived from range arithmetic, so a wrong zone does not merely mislabel a timestamp, it shifts which day a late evening event lands in, and the grouping then puts it in the wrong cell.
The README's time zone section is cut off in what is published here, so anything beyond the Time.zone mechanism has to be read from the source rather than inferred.
Translation keys are yours to define
The default views are prepared to perform translation lookups for month names and weekday labels, so the labels are already routed through I18n. What the gem does not do is ship translations for them.
The documented path is the rails-i18n gem, which already contains translations for many languages, plus your own keys. In a Rails 6 application you add rails-i18n to the Gemfile, declare the locales in config/application.rb, and then define the specific keys under a simple_calendar namespace:
config.i18n.available_locales = [:en, :de, :fr]
config.i18n.default_locale = :ende:
simple_calendar:
previous: "<<"
next: ">>"
week: WocheThe example is small on purpose. previous and next take symbols rather than words, which is a common choice for month navigation, and week carries the German label for the week view heading. Anything your locale files do not define falls back, so an untranslated locale renders the default strings rather than failing.
The project itself is a conventional gem layout: lib/ for the source, test/ with .rspec at the root, a Rakefile, an Appraisals file with a gemfiles/ directory for testing against multiple dependency sets, .standard.yml for the standard Ruby linter, and a simple_calendar.gemspec. Version 3.1.1 shipped on 2026-10-12 after 3.1.0 and 3.0.4 in January 2025, with the last push on 2026-10-13. The licence is MIT.
Editorial conclusion
Adopt simple_calendar when your Rails views need a calendar grid and you already hold the events as a collection, because the gem does the per day grouping and the month padding maths and stays out of your models. Do not adopt it expecting it to query for you, since events are filtered to the visible range by whatever you put in the controller, and do not skip scoping that query or you will load every record you own. Verify first that the generated views from rails g simple_calendar:views match your Bootstrap or Tailwind markup, since the gem ships its own partials rather than adapting to your component library.
Frequently asked questions
How do I add simple_calendar to a Rails application?
Add gem "simple_calendar" to your Gemfile and run bundle install. Optionally add the default stylesheet with *= require simple_calendar in application.css or @import "simple_calendar" in application.scss.
How do I show events inside a simple_calendar grid?
Pass them with the events option and accept a second block argument, as in month_calendar(events: @meetings) do |date, meetings|. The gem filters the collection by day before yielding.
Why does simple_calendar repeat every event on every day?
The objects you passed do not respond to the attribute method the gem expects, such as start_time. It then yields all objects on every day, which the README frames as a way to do your own filtering.
Can simple_calendar work without ActiveRecord?
Yes. The gem has no ORM dependency, so it works with ActiveRecord, Mongoid, any other ORM, or plain Ruby objects.
How do I customise the markup simple_calendar renders?
Run rails g simple_calendar:views to copy the views into app/views/simple_calendar, or point a single calendar at your own partial with the partial option.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/excid3-simple-calendar)Was this useful?