[ACCEPTED]-Rails date format in a text_field-ruby-on-rails

Accepted answer
Score: 64

Simple one time solution is to use :value option 2 within text_field call instead of adding something 1 to ActiveRecord::Base or CoreExtensions.

For example:

<%= f.text_field :some_date, value: @model_instance.some_date.strftime("%d-%m-%Y") %>
Score: 39

I added these to my initializers/time_formats.rb and it works great:

# Default format for displaying dates and times
Date::DATE_FORMATS[:default] = "%m/%d/%Y"
Time::DATE_FORMATS[:default] = "%m/%d/%Y"

Using 1 Ruby 1.9.3 and Rails 3.2.x

Score: 12

I spent some time looking in the code, and 32 changing the DATE_FORMAT won't work because 31 Rails never asks for the date format. Felix's 30 solution of setting :value does work, but 29 feels cumbersome: why should I have to set 28 the value of date-specific textfields by 27 hand? So this is a codesmell, and I wanted 26 something better.

Here's my short solution, but 25 then I'll explain it a bit because I'm hoping 24 somebody else will write something better:

MyModel < ActiveRecord::Base
  # ...
  self.columns.each do |column|
    if column.type == :date
      define_method "#{column.name}_before_type_cast" do
        self.send(column.name).to_s
      end
    end
  end
end

Somewhat 23 longer explanation: When a textfield is 22 setting the value attribute, it'll first 21 look in the options hash (which is why Felix's 20 solution works), but if it's not there, it'll 19 call form.object.check_in_date_before_type_cast, which (after some method_missing 18 magic) will call form.object.attributes_before_type_cast['check_in_date'], which will look up 'check_in_date' in 17 the @attributes hash inside form.object. My 16 guess is that the @attributes hash is getting 15 the direct MySQL string representation (which 14 follows the 'yyyy-mm-dd' format) before 13 it gets wrapped in a Ruby object, which 12 is why simply setting the DATE_FORMAT doesn't 11 work by itself. So the dynamic method creation 10 above creates methods for all date objects 9 that actually perform the typecast, allowing 8 you to format the date using ActiveSupport::CoreExtensions::Date::Conversions::DATE_FORMATS[:default].

This feels 7 a little dirty because the whole purpose 6 of '_before_type_cast' is to avoid a typecast, and 5 this is one of those "thar be dragons" monkeypatches. But 4 still, from what I can tell, it feels like 3 the best of what's around. Maybe somebody 2 else can do a little more digging and find 1 a better solution?

Score: 9

Expanding Kamil's answer a bit: add the 7 following method to application_helper.rb:

def date_mdY(date)
  if date.nil?
    ""
  else
    date.strftime("%m-%d-%Y")
  end
end

Then 6 you can modify Kamil's answer slightly:

<%= f.text_field :some_date, :value => date_mdY(@model_instance.some_date) %>

Then 5 you can use this helper method in any other 4 view.

I'm using the jQuery datepicker and 3 the date needed to be in a particular format 2 in order for the default date to be set 1 correctly. Thanks for the answer Kamil!

Score: 8

config/initializers/date_format.rb

ActiveSupport::CoreExtensions::Date::Conversions::DATE_FORMATS[:default] = '%m/%d/%Y'

view

<%= form.text_field :check_in_date, value: model.check_in_date.to_s %>

0

Score: 6

This one use location, in your form:

<%= f.text_field :date3, value: ( l @model.date3 if @model.date3? ) %>

In locations 1 en.yml (or es.yml)

en:
  date:
    formats:
      default: "%d/%m/%Y"
Score: 6

There are two parts to making this work, and 73 a third part to make it all work well. If 72 you just want to copy paste, go ahead and 71 scroll to the end.

Part 1: Getting Date to format itself as desired

There are a bunch of 70 core extensions to Date in ActiveSupport, but none of them create a new date class. If 69 you're working with Date, which is what Rails 68 returns when you have a date column in your 67 database, the method converting that date 66 into a string is Date#to_formatted_s. The ActiveSupport extensions add this 65 method, and they alias the to_s method to it, so 64 when you call Date#to_s (probably implicitly), you 63 get Date.to_formatted_s.

There are two ways to change the format 62 used by to_formatted_s:

  1. Pass the name of the format you would like used (this arg will be used to lookup the format in Date::DATE_FORMATS).
  2. Change the format that :default maps to. Since 61 to_formatted_s sets a default value of :default, when you call 60 Date#to_s without passing an argument you get format 59 specified by Date::DATE_FORMATS[:default]. You can override the default 58 on an application-wide basis like this:

    Date::DATE_FORMATS[:default] = "%m/%d/%Y"
    

I 57 have this set in an initializer like config/initializers/date_formats.rb. It's 56 crude and doesn't support changing with 55 your localizations, but gets Date#to_s to return 54 the desired format without any monkey patching 53 or explicitly converting your dates to strings 52 and passing in an argument.

Part 2: Getting user-inputted dates converted to Date objects

By default, your 51 ActiveRecord instance will cast date columns using good ol' ISO 8601 50 that looks like this: yyyy-mm-dd.

If it doesn't match 49 that format exactly (e.g., it's slash delimited), it 48 falls back to using Ruby's Date._parse, which is a 47 little more lenient, but still expects to 46 see days, months, then years: dd/mm/yyyy.

To get Rails 45 to parse the dates in the format you're 44 collecting them, you just need to replace 43 the cast_value method with one of your own. You can monkeypatch 42 ActiveRecord::Types::Date, but it's not much harder to roll your 41 own type inheriting from it.

I like using 40 Chronic to parse date strings from user input because 39 it puts up with more shit, like "Tomorrow", or 38 "Jan 1 99", or even bonkers input 37 like "5/6-99" (May 6, 1999). Since 36 Chronic returns an instance of Time and we're 35 overriding the supplied cast_value method, we need 34 to call to_date on it.

class EasyDate < ActiveRecord::Type::Date
  def cast_value(value)
    default = super
    parsed = Chronic.parse(value)&.to_date if value.is_a?(String)
    parsed || default
  end
end

Now we just need to tell 33 ActiveRecord to use EasyDate instead of ActiveRecord::Type::Date:

class Pony < ApplicationRecord
  attribute :birth_date, EasyDate.new
end

That works, but 32 if you're like me, you want to take on a 31 bunch more work right now so you can save 30 3 seconds in the future. What if we could 29 tell ActiveRecord to always use EasyDate 28 for columns that are just dates? We can.

Part 3: Make Rails handle this automatically

ActiveRecord 27 gives you all sorts of information about 26 your model that it uses to handle all of 25 the “magic” behind the scenes. One of the 24 methods it gives you us attribute_types. If you run that 23 in your console, you get back vomit — it's 22 kind of scary, and you just go “oh nevermind”. If 21 you dig into that vomit like some sort of 20 weirdo, though, it's just a hash that looks 19 like this:

{ column_name: instance_of_a_type_object, ... }

Not scary stuff, but since we're 18 starting to poke at internals, the inspected 17 output of these instance_of_a_type_object can end up looking obtuse 16 and “closed”. Like this:

#<ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Money:0x007ff974d62688

Thankfully, we 15 really only care about one: ActiveRecord::Types::Date, so we can 14 narrow it down:

date_attributes = attribute_types.select { |_, type| ActiveRecord::Type::Date === type }

That gives us a list of the 13 attributes that we want to upcycle to use 12 EasyDate. Now we just need to tell ActiveRecord 11 to use EasyDate on those attributes like 10 we did above:

date_attributes.each do |attr_name, _type|
  attribute attr_name, EasyDate.new
end

And that basically does it, but 9 we need to glue it all together. If you're 8 using ApplicationRecord you can drop this right into it and 7 be off to the races:

def inherited(klass)
  super
  klass.attribute_types.select { |_, type| ActiveRecord::Type::Date === type }.each do |name, _type|
    klass.attribute name, EasyDate.new
  end
end

If you don't want to 6 “pollute” ApplicationRecord, you can do like I did and create 5 a module and extend ApplicationRecord with it. If you're 4 not using ApplicationRecord, you'll need to create the module 3 and extend ActiveRecord::Base.

module FixDateFormatting
  # the above `inherited` method here
end

# Either this...
class ApplicationRecord < ActiveRecord::Base
  extend FixDateFormatting
end

# ...or this:
ActiveRecord::Base.extend(FixDateFormatting)

TL;DR — What to copy paste

If you aren't concerned with 2 the how and just want this to work, you can:

  1. Add gem "chronic" to your Gemfile and bundle install
  2. Make sure your models inherit from ApplicationRecord
  3. Copy the code below a file at config/initializers/date_formatting.rb
  4. Restart
  5. Everything should now Just Work™

Here's 1 the code to copy:

Date::DATE_FORMATS[:default] = "%m/%d/%Y"

module FixDateFormatting
  class EasyDate < ActiveRecord::Type::Date
    def cast_value(value)
      default = super
      parsed = Chronic.parse(value)&.to_date if value.is_a?(String)
      parsed || default
    end
  end

  def inherited(subclass)
    super
    date_attributes = subclass.attribute_types.select { |_, type| ActiveRecord::Type::Date === type }
    date_attributes.each do |name, _type|
      subclass.attribute name, EasyDate.new
    end
  end
end

Rails.application.config.to_prepare do
  ApplicationRecord.extend(FixDateFormatting)
end
Score: 2

My preferred way of handling this is with 12 virtual attributes. There's a short RailsCast on the subject (3m), but the upshot 11 is that virtual attributes will allow you 10 to use a non-standard format when displaying 9 model attributes in a form, or vice versa 8 (i.e., saving form data to a model).

Basically, rather 7 than creating a form field for the check_in_date attribute, you 6 can define a “virtual attribute” on the 5 model:

class Reservation
  def check_in_date_string
    check_in_date.strftime('%m-%d-%Y')
  end

  def check_in_date_string=(date_string)
    self.check_in_date = Date.strptime(date_string, '%m-%d-%Y')
  end
end

Now, you can create form fields that 4 correspond to check_in_date_string rather than check_in_date:

<%= f.text_field :check_in_date_string %>

That's it! No 3 need to explicitly specify a value option, and 2 when this form is submitted, the model will 1 be updated appropriately.

Score: 1

Go to your environment.rb file and add the 2 following:

ActiveSupport::CoreExtensions::Date::Conversions::DATE_FORMATS.merge!(
  :default => '%m-%d-%Y' ) 

Check the official documentation if you 1 feel like reading more :)

Score: 1

So I did something like this in the model.

def start_date
  super.strftime "%Y/%m/%d %H:%M"
end

Say 4 you have a column called start_date in model, and 3 want to display another format in the form, just 2 overwrite its value in model.

And nothing 1 needs to be changed in the form.

<%= f.text_field :start_date, class: 'form-control' %>
Score: 1

For people interested to format in a specific 4 location and not all over the project, several 3 answers advised to check if null and I want 2 to add that using try with strftime will help to make 1 it shorter.

            <%= f.fields_for :assets_depreciations do |asset| %>
              <tr>
                <td>
                  <%= asset.text_field :depreciation_date, value: asset.object.depreciation_date.try(:strftime, "%d-%m-%Y") %>
                </td>
              </tr>
            <% end %>
Score: 0

If you can set the value manually you can 1 use created_at.strftime("%m-%d-%Y") http://snippets.dzone.com/tag/strftime .

Score: 0

I had similar problems with time attributes/fields. So 14 one can follow this:

http://railscasts.com/episodes/32-time-in-text-field

And it works pretty 13 well.

But I dug into and found another interesting 12 solution. Kind of a ugly monkeypatch, but 11 in some cases it could be more useful that 10 the one from railscasts.

So I have a model 9 with a column named time (ofc it has a time 8 type). Here is my solution:

  after_initialize :init

  private
  def init
    unless time.nil?
      @attributes['time'] = I18n.l(time, format: :short)
    end
  end

As you can see 7 I format the variable which going to be 6 returned by time_before_type_cast method. So I have properly 5 formatted time in my text input (rendered 4 by FormBuilder), but if user puts something wrong like 3 10;23 I still have this and in next request it 2 will be rendered by FormBuilder::text_field. So user has an opportunity 1 to fix his miserable mistake.

Score: 0

A slight improvement of Tom Rossis answer 2 is to use the I18n.localize method, so the default will 1 always be in the current users locale:

initializers/date_time_i18n_formatter.rb

Date::DATE_FORMATS[:default] = ->(date) { I18n.localize date, format: :default }
Time::DATE_FORMATS[:default] = ->(time) { I18n.localize time, format: :default }

More Related questions