How do I validate a date in rails?

Ruby on-RailsRubyValidationDate

Ruby on-Rails Problem Overview


I want to validate a date in my model in Ruby on Rails, however, the day, month and year values are already converted into an incorrect date by the time they reach my model.

For example, if I enter February 31st 2009 in my view, when I use Model.new(params[:model]) in my controller, it converts it to "March 3rd 2009", which my model then sees as a valid date, which it is, but it is incorrect.

I would like to be able to do this validation in my model. Is there any way that I can, or am I going about this completely wrong?

I found this "Date validation" that discusses the problem but it never was resolved.

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

I'm guessing you're using the date_select helper to generate the tags for the date. Another way you could do it is to use select form helper for the day, month, year fields. Like this (example I used is the created_at date field):

<%= f.select :month, (1..12).to_a, selected: @user.created_at.month %>
<%= f.select :day, (1..31).to_a, selected: @user.created_at.day %>
<%= f.select :year, ((Time.now.year - 20)..Time.now.year).to_a, selected: @user.created_at.year %>

And in the model, you validate the date:

attr_accessor :month, :day, :year
validate :validate_created_at

private

def convert_created_at
  begin
    self.created_at = Date.civil(self.year.to_i, self.month.to_i, self.day.to_i)
  rescue ArgumentError
    false
  end
end

def validate_created_at
  errors.add("Created at date", "is invalid.") unless convert_created_at
end

If you're looking for a plugin solution, I'd checkout the validates_timeliness plugin. It works like this (from the github page):

class Person < ActiveRecord::Base
  validates_date :date_of_birth, on_or_before: lambda { Date.current }
  # or
  validates :date_of_birth, timeliness: { on_or_before: lambda { Date.current }, type: :date }
end 

The list of validation methods available are as follows:

validates_date     - validate value as date
validates_time     - validate value as time only i.e. '12:20pm'
validates_datetime - validate value as a full date and time
validates          - use the :timeliness key and set the type in the hash.

Solution 2 - Ruby on-Rails

Using the chronic gem:

class MyModel < ActiveRecord::Base
  validate :valid_date?

  def valid_date?
    unless Chronic.parse(from_date)
      errors.add(:from_date, "is missing or invalid")
    end
  end

end

Solution 3 - Ruby on-Rails

If you want Rails 3 or Ruby 1.9 compatibility try the date_validator gem.

Solution 4 - Ruby on-Rails

Active Record gives you _before_type_cast attributes which contain the raw attribute data before typecasting. This can be useful for returning error messages with pre-typecast values or just doing validations that aren't possible after typecast.

I would shy away from Daniel Von Fange's suggestion of overriding the accessor, because doing validation in an accessor changes the accessor contract slightly. Active Record has a feature explicitly for this situation. Use it.

Solution 5 - Ruby on-Rails

A bit late here, but thanks to "https://stackoverflow.com/questions/597328/how-do-i-validate-a-date-in-rails/4941755#4941755" I managed to write this validator, hope is useful to somebody:

Inside your model.rb

validate :date_field_must_be_a_date_or_blank

# If your field is called :date_field, use :date_field_before_type_cast
def date_field_must_be_a_date_or_blank
  date_field_before_type_cast.to_date
rescue ArgumentError
  errors.add(:birthday, :invalid)
end

Solution 6 - Ruby on-Rails

Since you need to handle the date string before it is converted to a date in your model, I'd override the accessor for that field

Let's say your date field is published_date. Add this to your model object:

def published_date=(value)
    # do sanity checking here
    # then hand it back to rails to convert and store
    self.write_attribute(:published_date, value) 
end

Solution 7 - Ruby on-Rails

Here's a non-chronic answer..

class Pimping < ActiveRecord::Base

validate :valid_date?

def valid_date?
  if scheduled_on.present?
    unless scheduled_on.is_a?(Time)
      errors.add(:scheduled_on, "Is an invalid date.")
    end
  end
end

Solution 8 - Ruby on-Rails

You can validate the date and time like so (in a method somewhere in your controller with access to your params if you are using custom selects) ...

# Set parameters
year = params[:date][:year].to_i
month = params[:date][:month].to_i
mday = params[:date][:mday].to_i
hour = params[:date][:hour].to_i
minute = params[:date][:minute].to_i

# Validate date, time and hour
valid_date    = Date.valid_date? year, month, mday
valid_hour    = (0..23).to_a.include? hour
valid_minute  = (0..59).to_a.include? minute
valid_time    = valid_hour && valid_minute

# Check if parameters are valid and generate appropriate date
if valid_date && valid_time
  second = 0
  offset = '0'
  DateTime.civil(year, month, mday, hour, minute, second, offset)
else
  # Some fallback if you want like ...
  DateTime.current.utc
end

Solution 9 - Ruby on-Rails

I recommend this method instead

  validate :birthday_must_be_a_date

  def birthday_must_be_a_date
    birthday&.to_date || errors.add(:birthday, :blank)
  rescue Date::Error
    errors.add(:birthday, :invalid)
  end

Solution 10 - Ruby on-Rails

Have you tried the validates_date_time plug-in?

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMegamugView Question on Stackoverflow
Solution 1 - Ruby on-RailsJack ChuView Answer on Stackoverflow
Solution 2 - Ruby on-RailsRogerView Answer on Stackoverflow
Solution 3 - Ruby on-RailsTxusView Answer on Stackoverflow
Solution 4 - Ruby on-RailsjoshuacronemeyerView Answer on Stackoverflow
Solution 5 - Ruby on-RailsunmultimedioView Answer on Stackoverflow
Solution 6 - Ruby on-RailsDaniel Von FangeView Answer on Stackoverflow
Solution 7 - Ruby on-RailsTripView Answer on Stackoverflow
Solution 8 - Ruby on-RailsKing'ori MainaView Answer on Stackoverflow
Solution 9 - Ruby on-RailsMathieu J.View Answer on Stackoverflow
Solution 10 - Ruby on-RailsRyan DuffieldView Answer on Stackoverflow