33
votes

When I use <%= f.datetime_select :somedate %> in a form, it generates HTML like:

<select id="some_date_1i" name="somedate1(1i)">  #year
<select id="some_date_2i" name="somedate1(2i)">  #month
<select id="some_date_3i" name="somedate1(3i)">  #day
<select id="some_date_4i" name="somedate1(4i)">  #hour
<select id="some_date_5i" name="somedate1(5i)">  #minute

When that form is submitted, the somedate1(<n>i) values are received:

{"date1(1i)"=>"2011", "date1(2i)"=>"2", "date1(3i)"=>"21", "date1(4i)"=>"19", "date1(5i)"=>"25"}

How can I convert that into a DateTime object?

I could write my own method to do this, but since Rails already is able to do the conversion, I was wondering if I could call that Rails method to do it for me?

I don't know where to look for that method.

I'm ultimately trying to solve "How to handle date/times in POST parameters?" and this question is the first step in trying to find a solution to that other problem.

7

7 Answers

28
votes

This conversion happens within ActiveRecord when you save your model.

You could work around it with something like this:

somedate = DateTime.new(params["date1(1i)"].to_i, 
                        params["date1(2i)"].to_i,
                        params["date1(3i)"].to_i,
                        params["date1(4i)"].to_i,
                        params["date1(5i)"].to_i)

DateTime::new is an alias of DateTime::civil (ruby-doc)

18
votes

The start of that code path, seems to be right about here:

https://github.com/rails/rails/blob/d90b4e2/activerecord/lib/active_record/base.rb#L1811

That was tricky to find! I hope this helps you find what you need

8
votes

Hi I have added the following on the ApplicationController, and it does this conversion.

    #extract a datetime object from params, useful for receiving datetime_select attributes
    #out of any activemodel
    def parse_datetime_params params, label, utc_or_local = :local
      begin
        year   = params[(label.to_s + '(1i)').to_sym].to_i
        month  = params[(label.to_s + '(2i)').to_sym].to_i
        mday   = params[(label.to_s + '(3i)').to_sym].to_i
        hour   = (params[(label.to_s + '(4i)').to_sym] || 0).to_i
        minute = (params[(label.to_s + '(5i)').to_sym] || 0).to_i
        second = (params[(label.to_s + '(6i)').to_sym] || 0).to_i

        return DateTime.civil_from_format(utc_or_local,year,month,mday,hour,minute,second)
      rescue => e
        return nil
      end
    end
3
votes

Had to do something very similar, and ended up using this method:

def time_value(hash, field)
  Time.zone.local(*(1..5).map { |i| hash["#{field}(#{i}i)"] })
end

time = time_value(params, 'start_time')

See also: TimeZone.local

2
votes

Someone else here offered solution of using DateTime.new, but that won't work in Postgresql. That will save the record as is, that is, as it was inserted in form, and thus it won't save in database as utc time, if you are using "timestamp without timezone". I spent hours trying to figure out this one, and the solution was to use Time.new rather than DateTime.new:

datetime = Time.new(params["fuel_date(1i)"].to_i, params["fuel_date(2i)"].to_i, 
                        params["fuel_date(3i)"].to_i, params["fuel_date(4i)"].to_i,
                        params["fuel_date(5i)"].to_i)
0
votes

I had this issue with Rails 4. It turned out I forgot to add the permitted params to my controller:

def event_params
  params.require(:event).permit(....., :start_time, :end_time,...)
end
-3
votes

This is the method I use - it returns the deleted keys as a new hash.

class Hash
   def delete_by_keys(*keys)
     keys.each_with_object({}) { |k, h| h[k] = delete(k) if include? k }
  end
end