Separate date and time form fields in Rails

25,869

Solution 1

Was looking at this today for a Rails project, and came across this gem:

https://github.com/shekibobo/time_splitter

Setting DateTimes can be a difficult or ugly thing, especially through a web form. Finding a good DatePicker or TimePicker is easy, but getting them to work on both can be difficult. TimeSplitter automatically generates accessors for date, time, hour, and min on your datetime or time attributes, making it trivial to use different form inputs to set different parts of a datetime field.

Looks like it would do the job for you

Solution 2

Do you have to have a text_field in the view?

As far as I can tell, you can have a date_time field and then just use two different input fields to set the different parts of the field.

form_for @event do |f|
  f.date_select :starts_at
  f.time_select :starts_at, :ignore_date => true
  f.submit
end

Since the rails date and time select helpers set five different parameters (starts_at(1i) for the year part, 2i for the month part, and so on), that means that the date_select only sets them for the date part, while if you pass :ignore_date => true to the time_select, it will only set the hour and minute part.

If you must have a text_field I'm not sure how to do it, but it might be possible to do using some jQuery magic before setting the datetime parameters before sending the form.

Solution 3

Using a date_select and time_select is a good way to go.

However, I wanted a text_field for the date (so I can use a JavaScript date picker).

Using strong_parameters or Rails 4+:

models/event.rb

# Add a virtual attribute
attr_accessor :start_at_date

views/events/_form.html.haml

- # A text field for the date, and time_select for time
= f.label :start_at
= f.text_field :start_at_date, value: (f.object.start_at.present? ? f.object.start_at.to_date : nil)
= f.time_select :start_at, :ignore_date => true

controllers/events_controller.rb

# If using a date_select, time_select, datetime_select
# Rails expects multiparameter attributes
# Convert the date to the muiltiparameter parts
def event_params
  if !!params[:event] && (params[:event]["start_at(4i)"].present? || params[:event]["start_at(5i)"].present?)

    if params[:event][:start_at_date].present?
      start_at_date = params[:event][:start_at_date]
    else
      start_at_date = Date.today
    end
    year  = start_at_date.match(/^(\d{4})[\-\/]/)[1]
    month = start_at_date.match(/[\-\/](\d{2})[\-\/]/)[1]
    day   = start_at_date.match(/[\-\/](\d{2})$/)[1]
    params[:event]["start_at(1i)"] = year
    params[:event]["start_at(2i)"] = month
    params[:event]["start_at(3i)"] = day
  end
  ...

Solution 4

Elegant solution may provide date_time_attribute gem:

class MyModel < ActiveRecord::Base
  include DateTimeAttribute

  date_time_attribute :starts_at
end

It will allow you to set starts_at_date and starts_at_time separately:

form_for @event do |f|
  f.text_field :starts_at_date
  f.text_field :starts_at_time
  f.submit
end

# this will work too:
form_for @event do |f|
  f.date_select :starts_at_date
  f.time_select :starts_at_time, :ignore_date => true
  f.text_field  :starts_at_time_zone
  f.submit
end

# or
form_for @event do |f|
  f.date_select :starts_at_date
  f.text_field  :starts_at_time
  f.submit
end

It will also allow you to play with time zones, use Chronic etc.

Share:
25,869
pgericson
Author by

pgericson

Updated on July 25, 2022

Comments

  • pgericson
    pgericson almost 2 years

    I have an ActiveRecord model Eventwith a datetime column starts_at. I would like to present a form, where date and time for starts_at are chosen separately (e.g. "23-10-2010" for date and "18:00" for time). These fields should be backed by the single column starts_at, and validations should preferably be against starts_at, too.

    I can of course muck around with virtual attributes and hooks, but I would like a more elegant solution. I have experimented with both composed_of (rdoc), and attribute-decorator (lighthouse discussion, github) without success.

    Below is a rough outline of what I would like..

    class Event < ActiveRecord::Base
      validates_presence_of :start_date
    end
    
    # View
    # On submission this should set start_date.
    form_for @event do |f|
      f.text_field :starts_at_date         # date-part of start_date
      f.text_field :starts_at_time_of_day  # time-of-day-part of start_date
      f.submit
    end
    

    Any help appreciated.

  • benvds
    benvds almost 13 years
    No it's not a design issue. The data should be a single column while the form builder should handle 2 different attributes for that same column.
  • benvds
    benvds almost 13 years
    Nice, seems to work and a good enough of a solution. But what I'd really like to see is a way to divide and reconstruct attributes around the form helper. E.g. before_form_build & before_form_validation.
  • Frost
    Frost almost 12 years
    Then I think you'd need to do some manual hacking on the params hash.
  • Jonathan Clark
    Jonathan Clark over 10 years
    Is there a way of setting timezone for this gem? It always saves in UTC.
  • asgeo1
    asgeo1 over 10 years
    In my testing it was using the configured time zone. Are you sure you were setting it correctly?
  • d_rail
    d_rail almost 10 years
    Are you sure you can mix date_select and text_field? I wasn't able to use a text_field and time_select and there is no documentation showing you can.
  • steve klein
    steve klein about 9 years
    I tried to use this gem and ran into a problem right away stackoverflow.com/questions/29858627/…
  • alexventuraio
    alexventuraio over 7 years
    Why do you use !! in the if comparison? What does that means?
  • d_rail
    d_rail over 7 years
    Converts the value into a boolean (stackoverflow.com/questions/524658/what-does-mean-in-ruby), probably not required.
  • alexventuraio
    alexventuraio over 7 years
    Yeah thats what I though but anyway thank you for the response. :)
  • Sidney
    Sidney over 6 years
    You don't need to include the DateTimeAttribute module, as said in the README of the repository of the gem: github.com/einzige/date_time_attribute#rails-users