dcsimg

Date Handling in Rails Applications

Brief walkthru of capturing, storing, and displaying days with Ruby on Rails.

Date handling is one of the web’s inevitables. Almost any web application you write is going to include some form of date-bound hacking. Whether it’s capturing user input via a web form, calculating the difference between two times, or fetching data via a date range, time and the web are closely linked.

Luckily for us, Rails make handling dates and times, like everything else, fairly trivial; that is, once you have an idea of how everything works.

I recently rewrote the section of our admin site in Rails that handles the creation of back issues. Since I had to hunt all over the web for a complete package of documentation to finalize this small task I thought it might be a good idea to share my experience.

In the interest of time we’ll assume you have a Rails app up and running and have controllers and models in place that you’re looking to add date-handling code to.

Form Helpers

Let’s start with data capture via a simple web form. When storing a back issue in the database the most important data we need to capture is its print date. As far as our back issues are concerned, print date is just a month and a year. To accomplish this, Rails provides a few FormHelpers for picking dates that we’ll use in our view.

/app/view/back_issues/new.rhtml

<%= error_messages_for :back_issue %>
<% form_for :back_issue do |f| %>

Print Month: <%= select_month(Date.today, :field_name => "print_month") %> 

Print Year: <%= select_year(Date.today, :start_year => 1999, :field_name => "print_year") %>

<%= submit_tag value='Create Backissue' %>
<% end %>

select_month() and select_year() generate pulldown menus for picking the issue’s month and year.

Pay special attention the :field_name option. It’s not documented well in the Rails API proper and will come in handy when you need to change the name of these select boxes to something other than the default of date[month] and date[year]; necessary if you are using multiple instances of these helpers in a single form.

For still greater control over a date submitted via a form, you might want to take look at select_datetime, which provides a pulldown for each element of a timestamp. Or for something a little more user friendly, check out the really nice Protoype-based Calendar Date Select.

Storing the Data

There’s no real reason for us to store the values from these two fields in separate database columns. Rather we’ll use the month and year values to construct a date in the controller and store the result in a single datetime field called print_date.

/app/controllers/back_issue_controller.rb

class BackIssuesController < ApplicationController

  def new
    @back_issue = BackIssue.new(params[:back_issue])
    return unless request.post?
    @back_issue.print_date = Date.new(params[:date]['print_year'].to_i, params[:date]['print_month'].to_i)
    @back_issue.save!
    flash[:notice] = "New back issue created."
    redirect_to "/covers/new?bi=" + @back_issue.id.to_s

	# In the event our validations fail
    rescue ActiveRecord::RecordInvalid
      render :action => 'new'
  end

end

Ok, that should do it. Once we’ve created an instance of @back_issue with the parameters from the form, we have access to print_date and can give it a newly constructed datetime with Date.new().

Date.new() can take three parameters (year, month, day). We’re taking the default of 1 on the day parameter by not passing anything to the method. In this example, we’re really only concerned with the month and year so the default is fine.

In reality, the new() method on our BackIssuesController is actually a bit more complex than this– hence the params[:back_issue] — but for the sake of simplicity, we’re just showing how to push a new date into the publish_date column from pulldown menus.

The redirect_to in this case point to our cover upload form, but you can point it to whatever destination you like following a successful save!.

Manipulating Dates

We don’t actually need to modify the date after it’s submitted to our controller, but if we did, Ruby has a few operators for quickly adjusting a Date object.

Operator Function
+(n) Add n days to the date
-(n) Subtract n days to the date
>>(n) Add n months to the date
<<(n) Subtract n months to the date

Pretty simple stuff. To demonstrate, let’s take these operators for a spin on the console.

>> d = Date.new(2007, 8, 23)
=> #
>> d.to_s
=> "2007-08-23"
>> yesterday = d-1
=> #
>> yesterday.to_s
=> "2007-08-22"
>> tomorrow = d+1
=> #
>> tomorrow.to_s
=> "2007-08-24"
>> lastmonth = d<<1
=> #
>> lastmonth.to_s
=> "2007-07-23"
>> nextmonth = d>>1
=> #
>> nextmonth.to_s
=> "2007-09-23"

Displaying Dates

Alright, now that we’ve written our back issue date to the database, all that’s left to do is display it in a view. We probably don’t want to display the date formatted as it’s stored: ‘YYYY-MM-DD’. Rather we want something like the current back issue page that displays the full month name and the year. To accomplish this we’ll use strftime().

If you’re familiar with PHP’s date() method, strftime() is similar, you pass the method a format string that defines how you want the date output. The difference is that while you pass PHP’s date() both a format and a timestamp, strftime() is a method that operates on a Date object. And everything in Rails is an object.

Rails automatically knows to treat our datetime columns in the database as Date objects, so, after you’ve fetched the back issue data from the database in the controller, formatting the date in the view is as simple as:

<%= @back_issue.print_date.strftime('%B %Y') %>

Which will print something like “January 2007″ to the screen.

For a full list of format codes for strftime(), see the table below.

Format Meaning
%a The abbreviated weekday name (“Sun”)
%A The full weekday name (“Sunday”)
%b The abbreviated month name (“Jan”)
%B The full month name (“January”)
%c The preferred local date and time representation
%d Day of the month (01..31)
%H Hour of the day, 24-hour clock (00..23)
%I Hour of the day, 12-hour clock (01..12)
%j Day of the year (001..366)
%m Month of the year (01..12)
%M Minute of the hour (00..59)
%p Meridian indicator (“AM” or “PM”)
%S Second of the minute (00..60)
%U Week number of the current year, starting with the first Sunday as the first day of the first week (00..53)
%W Week number of the current year, starting with the first Monday as the first day of the first week (00..53)
%w Day of the week (Sunday is 0, 0..6)
%x Preferred representation for the date alone, no time
%X Preferred representation for the time alone, no date
%y Year without a century (00..99)
%Y Year with century
%Z Time zone name
%% Literal “%” character

Rails Magic Wishlist

And that’s about it. Not too difficult but it’s actually a little more work that I’d like to do. Frankly, I wish there was some Rails magic that did the date handling in the controller for me.

Creating a new date and casting parameters in the controller isn’t how I’d like this to function — ultimately it doesn’t strike me as terribly DRY. Rather, I would like to have the option of passing the form fields to the controller as back_issue[print_date_month] and back_issue[print_date_year] and have Rails realize that these are components of my print_date field and compile these elements into a date for me. But all-in-all the solution we have here is reasonably clean and maybe I can build some of that magic into a plugin or helper at a later date.

Comments on "Date Handling in Rails Applications"

Thanks guy, that was helpful

Reply

Thanks again for the article. Awesome.

Reply

Major thanks for the blog article.Really thank you!

Reply

Do you mind if I quote a few of your posts as long as I provide credit and sources back to your site? My blog site is in the very same niche as yours and my users would certainly benefit from a lot of the information you provide here. Please let me know if this alright with you. Thank you!

Reply

Please let me know if you’re looking for a article writer for your blog. You have some really great articles and I think I would be a good asset. If you ever want to take some of the load off, I’d really like to write some articles for your blog in exchange for a link back to mine. Please shoot me an e-mail if interested. Thank you!

Reply

I’ll gear this review to 2 types of people: current Zune owners who are considering an upgrade, and people trying to decide between a Zune and an iPod. (There are other players worth considering out there, like the Sony Walkman X, but I hope this gives you enough info to make an informed decision of the Zune vs players other than the iPod line as well.)

Reply

The Zune concentrates on being a Portable Media Player. Not a web browser. Not a game machine. Maybe in the future it’ll do even better in those areas, but for now it’s a fantastic way to organize and listen to your music and videos, and is without peer in that regard. The iPod’s strengths are its web browsing and apps. If those sound more compelling, perhaps it is your best choice.

Reply

I want to express my love for your generosity supporting men and women that have the need for help with this situation. Your special dedication to getting the message along appears to be pretty beneficial and have consistently empowered guys like me to realize their ambitions. Your interesting tutorial signifies this much a person like me and substantially more to my office colleagues. Many thanks; from all of us.

Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>