4

I'm trying to enter the date '03/20/1985' into a text field called "birthday" and have it inserted into a database field with the column type "date".

When i enter 10/20/1985, i get the error "Birthday is invalid", but when i enter 20/10/1985, it works just fine.

From all the documentation i have been reading, chronic should parse '10/20/1985' as mm/dd/yyyy, but it seems that it's parsing it as dd/mm/yyyy.

How can i make this parse the date as mm/dd/yyyy?

/models/user.rb

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable, :authentication_keys => [:login]

  # Virtual attribute for authenticating by either username or email
  # This is in addition to a real persisted field like 'username'
  attr_accessor :login

  # Setup accessible (or protected) attributes for your model
  attr_accessible :email, :password, :password_confirmation, :remember_me, :username, :login, :first_name, :last_name, :home_phone, :cell_phone, :work_phone, :birthday, :home_address, :work_address, :position, :company

  validate :birthday_is_date
  validate :position, :presence => true

  require 'chronic'

  # validate the birthday format
  def birthday_is_date 
    errors.add(:birthday, "is invalid") unless Chronic.parse(birthday)
  end

  # validates email or username when logging in
  def self.find_first_by_auth_conditions(warden_conditions)
    conditions = warden_conditions.dup
    if login = conditions.delete(:login)
      where(conditions).where(["lower(username) = :value OR lower(email) = :value", { :value => login.downcase }]).first
    else
      where(conditions).first
    end
  end

end
Catfish
  • 18,876
  • 54
  • 209
  • 353

2 Answers2

3

If the value is stored as a date in the database, Rails will coerce the value from a string to a Ruby Date on assignment. I think it probably uses the built-in Date.parse method (docs):

Date.parse "2012-10-20"
# => #<Date 2012-10-20 ...>

Date.parse "20-10-2012"
# => #<Date 2012-10-20 ...>

Date.parse "10-20-2012"
# => ArgumentError: invalid date

This being the case, you want to avoid the coercion and get your hands on a raw string to parse with Chronic. This is an ideal use-case for virtual attributes. There are a few ways you can do it, something like this should get you started

class User < ActiveRecord::Base
  validate :birthday_is_date

  # an explicit implementation
  def birthday_string
    if @birthday_string
      @birthday_string
    elsif birthday.present?
      birthday.strftime("%d-%m-%Y")
    else
      ""
    end
  end

  # a shorter implementation
  def birthday_string
    @birthday_string || birthday.try(:strftime, "%d-%m-%Y")
  end

  def birthday_string=(value)
    @birthday_string = value
    self.birthday = parse_birthday
  end

  private

  def birthday_is_date
    errors.add(:birthday_string, "is invalid") unless parse_birthday
  end

  def parse_birthday
    Chronic.parse(birthday_string)
  end
end

And then use birthday_string in your forms, instead of birthday.

Andrew Haines
  • 6,574
  • 21
  • 34
  • This works well except when i enter a date of `1/5/1980` (Jan 5, 1980), it saves it to the database as `1980-05-01` and then displays it in my app as `05/01/1980`. How can i make it so it understands that i want 1/5/1980 to actually mean Jan 5 1980 instead of May 1 1980? – Catfish Dec 28 '12 at 22:32
  • Actually it seems to be working now. I noticed there was a warning in the log about not being able to mass assign birthday_string so i added :birthday_string to attr_accessible and it works great now. – Catfish Dec 28 '12 at 22:43
  • One thing i just noticed is that if a birthday is null, my edit page blows up with `undefined method 'strftime'`. Do you know how i can avoid this error with null birthday? – Catfish Dec 28 '12 at 23:57
  • You would just need to check for a nil `birthday` in the `birthday_string` method. I've added a couple of possible approaches to my answer. – Andrew Haines Dec 29 '12 at 14:11
1

Both work for me, maybe you need an update.

Also:

Chronic.parse '2/10/1985'
#=> 1985-02-10 12:00:00 +0800
Chronic.parse '2/10/1985', :endian_precedence => :little
#=> 1985-10-02 12:00:00 +0800
pguardiario
  • 53,827
  • 19
  • 119
  • 159