How to validate a date


#1

Hi,

I would like to validate a date (29 feb 2005 is invalid) and if it is an
invalid date display an error to the user, using the default way of
displaying errors. In my view I have a simple date_select:

<%= date_select (:listing, :start_date,
{:order => [:day, :month, :year],
:start_year => Time.now.year,
:end_year => Time.now.year+1}) %>

When I save data to the db, the field start_date should be validated and
if their is an error display it. So in my model I have tried:

def validate_on_create
year = start_date(1i)
month = start_date(2i)
day = start_date(3i)
unless Date::valid_date?(year.to_i, month.to_i, day.to_i)
errors.add(:start_date, “De startdatum moet een geldige datum
zijn.”)
end
end

But this doesn’t work, because I can’t access the values inside
start_date in the model. I get errors like that start_date is
multiparameter . I know that, but I can’t get to the values of the
parameters. Hope somebody can help.

Kind regards,

Nick


#2

Nick S. wrote:

end
end

But this doesn’t work, because I can’t access the values inside
start_date in the model. I get errors like that start_date is
multiparameter . I know that, but I can’t get to the values of the
parameters. Hope somebody can help.

Nick, applying posted parameters to your model using new, attributes=,
update_attributes, etc., will automatically set start_date to an
object of type Date based on the three component parameters. If the
date is invalid I think start_date will be set to nil, for which you
can check. If that’s not enough control, you’ll have to either arrange
to give validate_on_create access to the params hash so you can check
the values of the start_date(i) keys, or do the validation in a
separate date validation method called from the controller.

I wonder what the value of start_date.before_type_cast would be?
It’d be nice if this was a three-element array for easy validation.


We develop, watch us RoR, in numbers too big to ignore.


#3

On Dec 3, 2005, at 2:09 PM, Nick S. wrote:

              :end_year => Time.now.year+1}) %>
errors.add(:start_date, "De startdatum moet een geldige datum

Nick

I don’t know if this helps, but we’re validating the expiration date
of a credit card this way:

require ‘Date’

validates_each :payment_mm do |m, a, month|
if ((month.to_i < DateTime.now.month) && (m.payment_yyyy.to_i ==
DateTime.now.year))
m.errors.add(a, “Your credit card seems to be expired. Please
enter valid payment information.”)
end
end

Also, remember that if you operate on an instance’s values, you need
to refer to ‘self’. E.g., in the same model we send the order email
and then clear the credit card number before saving:

def before_save
OrderMailer.deliver_order(self)
self.payment_account="(not recorded)"
end

Phil


#4

Hi,

thank you both for the command. Helped to clear some issues up. I have
simplified my model code to, just to see the effect of the date:

def validate
logger.warn ("Start-- " + start_date.to_s)
end

I get a date back when I enter a valid date. But when I enter an invalid
date I get the error:

1 error(s) on assignment of multiparameter attributes

I have tried to catch the error like this:

def validate
begin
unless self.start_date
errors.add(:start_date, “De startdatum moet een geldige datum
zijn.”)
end
rescue
errors.add(:start_date, “De startdatum moet een geldige datum zijn.”)
end
end

But I keep getting the error. The error obviously means the date I
entered cann’t get converted to a valid date. So if I can catch this
error I’m done. But …

Kind regards,

Nick


#5

Getting tired of all the error messages, I thought I would throw the
date_select away in favor of:

<%= select_day (Time.now.day, :prefix => “listing”) %>
<%= select_month (Time.now.month, :prefix => “listing”) %>
<%= select_year (Time.now.year, :prefix => “listing”, :start_year =>
Time.now.year,
:end_year => Time.now.year+1) %>

Generates the same HTML code and now I get three separate params, not
related to each other, so no more multiparameter stuff.

I my model I added:

attr_accessor :day, :month, :year

def validate
if Date::valid_civil?(year.to_i, month.to_i, day.to_i) == nil
errors.add(:year, “geen geldige datum.”)
else
self.start_date = year.to_s + “-” + month.to_s + “-” + day.to_s
end
end

So now I should also be able to check if the date is before today. Only
problem I have right now is I can’t get Rails to draw a red box around
my three select fields (day, month and year). But if that is all, at
least my visitors are no longer able to enter an invalid date.

My advice stay away from date_select if you want to do some serious date
checking, at least until they introduce a decent date validator, like
validate_date_of or something like that.

Kind regards,

Nick


#6

On Sat, 3 Dec 2005, Nick S. wrote:

their is an error display it. So in my model I have tried:

But this doesn’t work, because I can’t access the values inside start_date
in the model. I get errors like that start_date is multiparameter . I know
that, but I can’t get to the values of the parameters. Hope somebody can
help.

say you have a table like:

create table moments (
id serial,
moment timestamp
);

and a controller like this:

[ahoward@localhost date_validation]$ cat
app/controllers/moment_controller.rb
class MomentController < ApplicationController
scaffold :moment
end

then a model like this will solve your problem

[ahoward@localhost date_validation]$ cat app/models/moment.rb
class ::Time
class << self
def normalized? t, values
tvalues = %w(year month day hour min sec usec).map{|m| t.send
m}
values.zip(tvalues){|vin,vout| return true if vin != vout}
return false
end
%w( local mktime gm utc ).each do |method|
eval <<-code
alias org#{ method }_ #{ method }
def #{ method }(*a, &b)
t = org#{ method }_(*a, &b)
t.instance_eval{@original_arguments = a}
t.instance_eval{@normalized = self.class.normalized? self,
a}
t
end
code
end
end
attr_reader ‘normalized’
alias_method ‘normalized?’, ‘normalized’
attr_reader ‘original_arguments’
end

class Moment < ActiveRecord::Base
protected
def validate *a, &b
t = moment
if t.nil? or t.normalized?
values = t.original_arguments
errors.add self.class.to_s, “bad time from
<#{values.inspect}>”
end
super
end
end

though you’d want to put the Time code somewhere else in practice…

an attempt to enter Feb 31st on my system results in an error page that
looks like

 Editing moment
 1 error prohibited this moment from being saved

 There were problems with the following fields:

     * Moment bad time from <[2005, 2, 31, 11, 48]>

i’ve posted this fix in the past but, for some reason, there was not
much
interest. read this thread for more info:

http://wrath.rubyonrails.org/pipermail/rails/2005-March/004619.html

this technique could easily be used to make a nice time/date validator
and is,
in fact, extensible for any multi-param rails object: the approach is
always
the same - try ctor, munge inputs if required, remember the original
arguments
for later access in validation.

kind regards.

-a

===============================================================================
| ara [dot] t [dot] howard [at] noaa [dot] gov
| all happiness comes from the desire for others to be happy. all misery
| comes from the desire for oneself to be happy.
| – bodhicaryavatara


#7

removed_email_address@domain.invalid wrote:

On Sat, 3 Dec 2005, Nick S. wrote:

I would like to validate a date (29 feb 2005 is invalid) and if it is an
invalid date display an error to the user, using the default way of
displaying errors. In my view I have a simple date_select:

[snip]

app/controllers/moment_controller.rb
class MomentController < ApplicationController
scaffold :moment
end

then a model like this will solve your problem

[snip]

arguments
for later access in validation.

kind regards.

-a

Hi, Ara - having discussed this with you over eight months ago, I would
never have believed that Rails would reach 1.0 without sorting it out.
Maybe date_select, like scaffolding, isn’t intended for production use.

regards

Justin


#8

— Nick S. removed_email_address@domain.invalid wrote:

             {:order => [:day, :month, :year],

month = start_date(2i)
values inside
start_date in the model. I get errors like that
start_date is
multiparameter . I know that, but I can’t get to the
values of the

I made a date validator:
http://www.garbett.org/?q=node/27, as well as several
other helpers. This code is free for the taking. No
license just pure public domain. I relinquish all
copyrights and all that other legal stuff on this
code. May it help you.

With this one can just put
validates_date :birthdate

in the model, and the problem is solved.

Code from site:

require ‘parsedate’

ActiveRecord::Base.class_eval do

def self.validate_date(string)
data = ParseDate.parsedate(string)
0.upto(2) { |i| raise “Bogus date [#{string}]
encountered” if data[i].nil? }
raise “Pre-1800 date [#{string}] encountered” if
data[0] < 1800
raise “Invalid date [#{string}] encountered” if
nil==Date.new(data[0],data[1],data[2])
return true
rescue Exception => e
logger.error(“Date format exception [#{e}]”)
return false
end

Configuration options:

* message - A custom error message (default is:

“can’t be blank”)

* on - Specifies when this validation is active

(default is :save, other options :create, :update)

* if - Specifies a method, proc or string to call

to determine if the validation should

occur (e.g. :if => :allow_validation, or :if =>

Proc.new { |user| user.signup_step > 2 }). The

method, proc or string should return or evaluate

to a true or false value.
def self.validates_date(*attr_names)
configuration = { :message => ‘Invalid date, use
(MM/DD/YYYY)’, :on => :save }
configuration.update(attr_names.pop) if
attr_names.last.is_a?(Hash)
attr_names.each do |attr_name|
send(validation_method(configuration[:on])) do
|record|
unless configuration[:if] &&
!evaluate_condition(configuration[:if], record)
date =
record.attributes_before_type_cast[attr_name.to_s]
if not date.nil? and not date.class == Date
and date.length > 0 and date.class == String and not
self.validate_date(date)

record.errors.add(attr_name,configuration[:message])
end
end
end
end
end
end


Do You Yahoo!?
Tired of spam? Yahoo! Mail has the best spam protection around
http://mail.yahoo.com


#9

On Mon, 5 Dec 2005, Justin F. wrote:

Hi, Ara - having discussed this with you over eight months ago, I would
never have believed that Rails would reach 1.0 without sorting it out. Maybe
date_select, like scaffolding, isn’t intended for production use.

indeed. particularly when there are several viable alternatives that
address
the issue floating around. i’m not going to admit in public that i only
remember i’d solved this once before until after searching the
archives
and stubmling on the thread between you and i :wink:

cheers.

-a

===============================================================================
| ara [dot] t [dot] howard [at] noaa [dot] gov
| all happiness comes from the desire for others to be happy. all misery
| comes from the desire for oneself to be happy.
| – bodhicaryavatara