Rails Date Validation – Step by Step
Update 5/31/2008: Use ActiveSupport::CoreExtensions::Date::Conversions::DATE_FORMATS
I have invested some time to get Rails date validation to work (whoo hoo!).
Without further ado, here are the step by step instructions:
1. Download Rails Date Kit from my site. Note that I got the original kit from http://www.methods.co.nz/rails_date_kit/rails_date_kit.html.
Extract the files in rails_date_kit_1.2.0.tar.gz to <your application>/vendor/plugins/rails_date_kit.

2. Get the Validates Date Time plugin by running the following command in your application directory:
script/plugin install http://svn.viney.net.nz/things/rails/plugins/validates_date_time
You should have a new directory in your <your application>/vendor/plugins/validates_date_time

3. Put the necessary files in the right places.
Copy vendor/plugins/rails_date_kit/calendar.js to public/javascripts
Copy vendor/plugins/rails_date_kit/calendar.css to public/stylesheets
Copy vendor/plugins/rails_date_kit/calendar_prev.png to public/images
Copy vendor/plugins/rails_date_kit/calendar_next.png to public/images
Copy vendor/plugins/rails_date_kit/date_helper.rb to app/helpers
4. Include the css and javascript files for the calendar in your page header (e.g. view/layout/tasks.html.erb):
<%= stylesheet_link_tag ‘calendar.css’ %>
<%= javascript_include_tag ‘calendar’ %>
My application uses application.rhtml, thus I just added the lines there.
5. Create a new file called date_formats.rb in config/initializers. All you need to add is:
ActiveSupport::CoreExtensions::Date::Conversions::DATE_FORMATS.merge!(:default_date => ‘%m/%d/%Y’)
Note the capital Y in ‘%m/%d/%Y’. Obviously, you can add more date formats as needed.
Update: Adjust the date format accordingly to your locale. For example, use ‘%d/%m/%Y’ for displaying date/month/year.
6. Use the following code to add the date field in your input form (e.g. view/tasks/new.html.erb):
<%= date_field :task, :due_date, :format => ActiveSupport::CoreExtensions::Date::Conversions::DATE_FORMATS[:default_date], :value => @task.due_date %>
You can also wrap the whole ‘ActiveSupport::CoreExtensions::Date::Conversions::DATE_FORMATS[:default_date]‘ into a function in app/helpers/application_helper.rb.
6. Tell your model to use en-us date format by adding the following line:
ValidatesDateTime.us_date_format = true #use mm/dd/yyyy format
Update: Set ValidatesDateTime.us_date_format to ‘false’ if your date format is not month/date/year.
7. Validate away! ![]()
Here’s an example of my model validation:
Class Task < ActiveRecord::Base
.
.
validates_date :due_date, :allow_nil => true
.
.
End
Enjoy!
Post any questions in the comment.
June 23rd, 2008 at 2:46 pm
Hi Handy,
Thanks for the great tutorial. It is really very usefull, however I am getting one error on your implementation. Whne I place ValidatesDateTime.us_date_format = true #use mm/dd/yyyy format in my model I receive the following error:
uninitialized constant Visit::ValidatesDateTime
Have you any ideas, I’m pulling out my hair!
Thanks again.
j
June 23rd, 2008 at 2:51 pm
Hi Handy,
I installed the plugin as a above, instead of downloading it, and the error has disappeared but there are only nulls being writted to the database. Any ideas?
j
June 23rd, 2008 at 3:40 pm
Handy,
I got all nulls untill I did the following:
ValidatesDateTime.us_date_format = false #use mm/dd/yyyy format
Hey Presto, it worked.
Laters,
j
June 24th, 2008 at 12:09 am
John,
This post is used mainly for en-us locale. If you use a different locale, you’re correct that ValidatesDateTime.us_date_format = false.
Also, don’t forget to change step 5:
ActiveSupport::CoreExtensions::Date::Conversions::DATE_FORMATS.merge!(:default_date => ‘%m/%d/%Y’)
The default date format for display should be changed as well.
Let me know if you have other question.
Handy
July 20th, 2008 at 3:29 am
Ok, the ruby code did not go through on my previous post.
I want to embed the calendar in a Rails form using
“form_for @posting do |f|”?
Thanks,
Cheri
July 21st, 2008 at 2:37 am
Cheri,
Interesting idea… I’ve never thought about doing the embedded calendar since I think the form looks more clean for my project. Different projects have different requirements, correct?
The date field is calling calendar_open from public/javascripts/calendar.js.
To show the calendar, you may try to call calendar_open with the same parameters on a hidden date field. When the user clicks on the date, the calendar should update the hidden field, which can be submitted when the user accepts the form.
Let me know if this works and I’ll be happy to put a link to your investigation.
Handy
August 8th, 2008 at 4:26 am
Hi Handy,
One problem i’ve come across is that it seems impossible to change a date to nil from a valid date once it is set – any ideas ?
August 9th, 2008 at 4:20 pm
Colin,
I can still delete a valid date from my form.
A couple things to check:
- The field value should be empty when the user saves the form. Can you verify that the controller correctly get an empty string from the field?
- Are you sure the date is not protected field?
Handy
February 10th, 2009 at 6:00 pm
Handi,
Using Rails 2.2.0 Other versions were not tried.
Validates_date_time has some behaviors that are problematic. It does not appear possible to allow validation of a date_time field to distinguish between a nil entry and an entry that Rails rejects by substituting a nil for that value. For example setting :allow_nil => true causes both “record.field = nil” and “record.field = ‘a funny date’” to pass validation. :allow_nil => false ,the default, rejects both entries. However, a field for which an optional entry must be validated is not handled by validates_date_time.
I think the problem is that if time zone conversion is in effect, Rails checks in ActiveRecord::AttributeMethods for a valid date_time before storing the raw_value in @attributes. “#{attr_name}_before_type_cast” is a misnomer. It returns @attributes[attr_name]. But date or time fields stored in @attributes have already been parsed into an appropriate format or replaced with nil. This prevents validates_date_time from doing its own validation and issuing an appropriate error message. If the DB is set up to allow nil for that field then no complaint will ever be issued. A near miss date would be accepted and stored as nil without warning! The only validations that can be done are that a date is sane from the validation perspective or that a nil will be stored in the database on save or update. Validation cannot check for an unsupplied date or time versus an invalid string.
You can see the behavior of Rails by making the following change in the validates_date_time code:
def validates_date(*args)
…
validates_each(attr_names, options) do |record, attr_name, value|
raw_value = record.send(”#{attr_name}_before_type_cast”)
rv = raw_value.nil ? ‘nil’ : raw_value
puts “Attr Name: #{attr_name}, Value: #{value}, Raw Value: #{rv}”
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
if (!raw_value.blank? and !value) || (raw_value.blank? and !allow_nil)
…
end
Run some unit tests against a model with validates_date_time properly installed and with the following in place:
validates_date :start, :allow_nil => true
Validating a record with start set to ‘a funny date’ will pass. This will appear in the test run:
Attr Name: start, Value: a funny date, Raw Value: nil
Inspection of the record after the assignment will show:
… start: nil, …
Assuming that time zone conversion is desirable, I do not know how to get around this except by changing the Rails behavior, itself. For example one could add a @raw_date_time hash to the model and store the unparsed value for the attribute as @raw_date_time[attr_name]. Then validate_date_time could access @raw_date_time with a getter for the raw_value rather than using “#{attr_name}_before_type_cast”. the relevant code is in attribute_methods.rb of the Rails code:
def define_write_method_for_time_zone_conversion(attr_name)
method_body = <<-EOV
def #{attr_name}=(time)
@raw_date_time[#{attr_name}] = time #<<<<< Added code.
# @raw_date_time must have been defined
# for the instance, of course.
unless time.acts_like?(:time)
time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
end
time = time.in_time_zone rescue nil if time
write_attribute(:#{attr_name}, time)
end
EOV
evaluate_attribute_method attr_name, method_body, “#{attr_name}=”
end
Any thoughts on this? It seems date & time validation is still a problem! I would love to find a good solution or to be proven wrong.
BTW. The methods for validates_date, validates_time, and validates_date_time share a lot of code. A good place to DRY the code base.
Doc Pneumo
February 10th, 2009 at 11:16 pm
Handy,
After submitting my last post, I found the validates_timeliness plugin at git://github.com/adzap/validates_timeliness.git. This appears to answer my concerns and more. I’ve skimmed the code but haven’t used it yet. Will swap it in for validates_date_time in a project I’m working on. Will report any issues I find.
Doc Pneumo
February 11th, 2009 at 4:16 pm
validates_timeliness seems to work well on the first pass.
At the moment I’m developing in a Windows environment. Loading the plugin had lots of problems. However, doing ‘gem install validates_timeliness’ worked fine. The current version is 1.1.5 .
Remember to put this in config/environment: config.gem “validates_timeliness”
Doc Pneumo
March 18th, 2009 at 9:46 am
Nice work ! and nice doc !
I’ve followed the step-by-step doc and everything went fine !
May 27th, 2009 at 6:18 am
Handy,
I’ve followed the tutorial step-by-step, and wrapped
ActiveSupport::CoreExtensions::Date::Conversions::DATE_FORMATS.merge!(:default_date => ‘%m/%d/%Y’)
into a function into the application helper file:
module ApplicationHelper
def calendar_format(date_field)
ValidatesDateTime.us_date_format = true #use mm/dd/yyyy format
ActiveSupport::CoreExtensions::Date::Conversions::DATE_FORMATS[date_field]
end
end
However, trying to use the following in ../views/attorneys/edit.html.erb causes an error:
calendar_format(@attorney.current_retroactive_date) %>
The error I receive is:
undefined method `date_field’ for #
I don’t understand it, since I have copied the included date_helper.rb file to ../app/helpers/date_helper.rb and it clearly defines the date_field:
module DateHelper
nclick => “event.cancelBubble=true;this.select();calendar_open(this,{format:’#{format}’,images_dir:’/images’,month_names:#{months},day_names:#{days}})”,
…
def date_field(object_name, method, options={})
format = options.delete(:format) ||
ActiveSupport::CoreExtensions::Date::Conversions::DATE_FORMATS[:default] ||
‘%d %b %Y’
if options[:value].is_a?(Date)
options[:value] = options[:value].strftime(format)
end
months = Date::MONTHNAMES[1..12].collect { |m| “‘#{m}’” }
months = ‘[' + months.join(',') + ']‘
days = Date::DAYNAMES.collect { |d| “‘#{d}’” }
days = ‘[' + days.join(',') + ']‘
options = {:onfocus => “this.select();calendar_open(this,{format:’#{format}’,images_dir:’/images’,month_names:#{months},day_names:#{days}})”,
}.merge(options);
text_field object_name, method, options
end
end
Do you have any idea why this may be the case? I have restarted my server, as I thought that may be the issue, but no such luck. I am running:
ruby 1.8.6 (2008-08-11 patchlevel 287) [universal-darwin9.0]
Rails 2.3.2
Thanks for any assistance in advance,
Ahad.
May 27th, 2009 at 6:19 am
the part of the error left out is due to formatting is:
undefined method `date_field’ for #
May 27th, 2009 at 6:19 am
ActionView::Helpers::FormBuilder:0×2425584 with angle brackets around it (happened again)
May 27th, 2009 at 6:22 am
Hah, I resolved it by understanding that I am very new to this material and made a rookie mistake. Just needed to take the “f.” portion out. Thanks for this great tutorial!
May 27th, 2009 at 11:37 am
I’m glad that my tutorial works well for you.