33

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.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Zabba
  • 64,285
  • 47
  • 179
  • 207

7 Answers7

30

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)

Joe Flynn
  • 6,908
  • 6
  • 31
  • 44
Dylan Markow
  • 123,080
  • 26
  • 284
  • 201
  • 1
    unfortunately this causes issues with utc time with postgresql – JohnMerlino Apr 29 '14 at 00:03
  • 1
    @JohnMerlino This is late, but you can use Ruby to convert the time to UTC (or whatever time zone you want) before you save it to Postgres, and convert it back to X locale when you retrieve it from the database. As a matter of fact, storing in UTC and converting client-side is the [general thing that you do](http://stackoverflow.com/a/6158432/1986871). – Chris Cirefice Jul 24 '15 at 19:13
19

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

John Douthat
  • 40,711
  • 10
  • 69
  • 66
Chris Cherry
  • 28,118
  • 6
  • 68
  • 71
8

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
viniciuscb
  • 123
  • 1
  • 7
  • 1
    As a note, the class of exp in that rescue clause is StandardError not Exception. I would consider changing it to either "e" or "error" as to avoid any confusion. – adantj Feb 20 '14 at 20:20
3

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

Ja͢ck
  • 170,779
  • 38
  • 263
  • 309
2

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)
JohnMerlino
  • 3,900
  • 4
  • 57
  • 89
  • When I do this I can't access my parameters. When I type "params" into byebug it shows the parameters that come in from a post. When I type "params["fuel_date(1i)"].to_i" it gives me "nil". Any idea why? – camdixon Aug 07 '14 at 14:20
  • 1
    @camdixon look at your params hash coming in from the POST from the form. That will tell you the exact string to use. In my case, it was fuel_date because that was the symbol I assigned to datetime_select, but in your case you may be using a different symbol. – JohnMerlino Aug 07 '14 at 19:33
0

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
Zsolt
  • 253
  • 3
  • 15
-4

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
traHfo
  • 83
  • 1
  • 3