How to parse time field into hours, minutes and seconds? - ruby-on-rails

How would I parse my start_at column to be three different fields for hours, minutes and seconds instead of just one?
Note: I want to keep it as one column and not make three different ones.
Here is my code:
Table:
class CreateTimers < ActiveRecord::Migration
def change
create_table :timers do |t|
t.time :start_at
t.timestamps
end
end
end
Form:
<%= form_for(#timer) do |f| %>
<div class="form-inputs">
<%= f.text_field :start_at %>
</div>
<div class="form-actions">
<%= f.submit 'Start', :class => 'btn-primary span3' %>
</div>
<% end %>

If you just want to split time to three separate inputs you can use the time_select helper.
Otherwise use the strftime method; Check http://strfti.me for more help.

You don't say what format your time values are in. Because users love to be different, if you give them a free-form text field, odds are really good you'll get data in varying formats, so you'll have to be flexible.
You can try using DateTime's parse method, which provides some flexibility for the formats.
DateTime.parse('2001-02-03T04:05:06+07:00')
#=> #<DateTime: 2001-02-03T04:05:06+07:00 ...>
DateTime.parse('20010203T040506+0700')
#=> #<DateTime: 2001-02-03T04:05:06+07:00 ...>
DateTime.parse('3rd Feb 2001 04:05:06 PM')
#=> #<DateTime: 2001-02-03T16:05:06+00:00 ...>
There's also the Chronic gem, which allows even more flexibility in how the values can be entered.
Once you have a DateTime or Time value, you can use that object's methods to get at the hour, minute and second fields:
now = Time.now # => 2012-10-03 07:50:26 -0700
now.hour # => 7
now.min # => 50
now.sec # => 26
or:
require 'date'
now = DateTime.now # => #<DateTime: 2012-10-03T07:52:53-07:00 ((2456204j,53573s,622304000n),-25200s,2299161j)>
now.hour # => 7
now.min # => 52
now.sec # => 53

Check out strftime at http://www.ruby-doc.org/core-1.9.3/Time.html

For e.g.
start_at - 2011-04-26 04:14:56 UTC
For hours:
start_at.strftime("%H") = "04"
For minutes:
start_at.strftime("%M") = "14"
For seconds:
start_at.strftime("%S") = "26"

start_at.strftime("%H %M %S")
More detailed documentation is at: http://www.ruby-doc.org/core-1.9.3/Time.html

Related

no implicit conversion of ActiveSupport::TimeWithZone into Integer

I want to set default time to 2 hours from now. So I wrote this peace of code:
<%= f.datetime_select :starts_at, :default => { :hour => 2.hours.from_now, :minute => 0 }, :order => [:day, :month, :year] %>
I've got an error:
no implicit conversion of ActiveSupport::TimeWithZone into Integer
2.hours.from_now yields a ActiveSupport::TimeWithZone object but hour is expecting an Integer.
:hour => 2.hours.from_now.hour
I tried this and it works fine for me. Do you have a validation on your model which is causing it to fail?
Also this will not give you exactly 2 hours since you are setting the minute variable.
Have you tried with?
:hour => 2.hours.from_now.hour
I edited because you need the absolute hour.

Three text fields for one database entry for rails

I currently have one text field for a date entry, I am trying to split the year, month and day up into three individual entries, seperated by '/'. The original text entry looks like:
<%= f.text_field :date, :placeholder => 'YYYY/MM/DD' %>
I would like to split this into three text_fields, and append them together, and put it into the date entry in the database.
How can I do this?
You often use textfields for dates because you then only needs one field. When you are having three different fields should you consider using select instead. Rails have a the date helper method date_select, so it would be somethink like this:
<%= f.date_select :date %>
This creates one select for years, one for months and one for days.
You can read more on http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#method-i-date_select
Add three virtual attributes to the model:
attr_accessor :form_month, :form_day, :form_year
Then put the following in the controller, not the model:
def create
# ...
form_date = [ params[:form][:form_month], \
params[:form][:form_day] ,
params[:form][:form_year] ].join("/")
#my_model.date = Date.parse(form_date)
# ... save and return ...
end
It would be a good idea to manually check each form parameter for validity. Date#parse may spit out the incorrect date if fed an incomplete date string.
Date.parse "2005/11/4"
# => Fri, 04 Nov 2005
Date.parse "/11/4"
# => Mon, 04 Nov 2013
Try to use https://github.com/plataformatec/simple_form
<%= f.input :deadline, :start_year => Date.today.year, :end_year => Date.today.year + 1, :order => [:day, :month, :year] %>
Do you have many options.

In Rails/Formtastic how do I restrict hours in the time input field

I have a requirement to display duration as HH:MM in Rails as two select boxes one for hours and other for minutes. Restrict the hours to max of 4:00. 'duration'(column type:integer) is stored as minutes in database.
And I am using formtastic.
If I dispaly input as time, I am able to get the output format as drop down HH:MM.
"<%= f.input :duration, :label => 'Duration', :as=>:time, :minute_step => 15, :hint=>"Measured in hours", :end => 10 %>"
How do I restrict the hours shown for selection( only 01, 02, 03, 04 should be displayed in the drop down for hours)?
Please let me know if there are any options to be specified for Rails/Formtastic. Or is there any better way to handle this scenario?
I don't see any options to limit it through Formtastic. You could just display the input as a select and pass it the options you want explicitly.
<%= f.input :hours, :as=>:select, :collection => (0..4) %>
<%= f.input :minutes, :as=>:select, :collection => [0,15,30,45] %>
Then you'll probably need to add these virtual attributes in the model:
before_save :set_duration
def set_duration
self.duration = #hours * 60 + #minutes
end
def hours
self.duration / 60;
end
def minutes
self.duration % 60;
end
def hours=(h)
#hours = h
end
def minutes=(m)
#minutes = m
end
def duration=(d)
#hours = d / 60
#minutes = d % 60
self.set_duration
end
And you might want to look at this answer to get them to look more like the original.
There might be some clever, quicker way to do this, but this is the first thing that comes to mind.

multiparameter error with datetime_select

I have the following code in my form.
<%= f.datetime_select(:date_time, :prompt => {:day => 'Day', :month => 'Month', :year => 'Year'}, :start_year => Date.today.year, :end_year => Date.today.year + 2, :minute_step => 15, :include_blank => false) %> if either one is blank.
When one of the fields is left blank, I get:
1 error(s) on assignment of multiparameter attributes
The params that are being passed are:
{"utf8"=>"✓",
"authenticity_token"=>"kQpfsj5RxnDtxkvBdwPEFnX1fY6euKnMQeDRAkvJvIE=",
"event"=>{"description"=>"",
"venue"=>"",
"street"=>"",
"city"=>"",
"country_id"=>"",
"date_time(1i)"=>"",
"date_time(2i)"=>"",
"date_time(3i)"=>"",
"date_time(4i)"=>"00",
"date_time(5i)"=>"00",
"ticket_url"=>""},
"x"=>"94",
"y"=>"12"}
Anyone know why this is occurring?
There seems to be a "dirty" fix for this at this link, but perhaps there is a better solution in Rails 3?
Christian. This is a bug in Rails that checks the database to infer the type needed for the multiparameter attributes. My guess is that your "date_time" attribute is not associated with a time column in your database.
I recently tackled this problem where I wanted a non-database attribute to accepted multiparameter attributes, this was the best solution I could come up with:
I found myself wanting to set an attr_accessor to handle passing a date to my model in a form_for tag with the f.datetime_select helper. So this is what I had:
Model:
attr_accessor :my_time
View:
<%= f.datetime_select :my_time %>
Unfortunately when I submit my form I get this:
1 error(s) on assignment of multiparameter attributes
Well it turns out that this is actually a Rails bug a ticket for which has been submitted. In the meantime how do we make this work? The only solution I could find that was remotely attractive was to make use of composed_of as a replacement for attr_accessor. so...
Model:
composed_of :my_time,
:class_name => 'Time',
:mapping => %w(Time to_s),
:constructor => Proc.new{ |item| item },
:converter => Proc.new{ |item| item }
I know almost nothing about the composed_of method so you should probably do your own reading on it, but what I do know is that it creates both a reader and writer for the given instance variable, and more importantly, the setter accepts multiparameter attributes. How I chose the options:
class_name: the name of our expected class. In this case, Time
mapping: the first argument is the class and the second argument seems to work with any method that an instance of the class responds to. I chose to_s
constructor: Not really sure how this is supposed to work. Seems to be called when #my_time is nil.
converter: Not really sure how this is supposed to work. Seems to be called when from my_time=, but doesn't seem to be applied with mass assignment.
One problem I ran into with this solution was that times were getting set in UTC instead of the environment's time zone. So unfortunately we cannot use my_time directly, but instead need to convert it to the proper time zone:
Time.zone.parse(my_time.to_s(:number))
What Does ActiveRecord::MultiparameterAssignmentErrors Mean?
def initialize(attributes={})
date_hack(attributes, "deliver_date")
super(attributes)
end
def date_hack(attributes, property)
keys, values = [], []
attributes.each_key {|k| keys << k if k =~ /#{property}/ }.sort
keys.each { |k| values << attributes[k]; attributes.delete(k); }
attributes[property] = values.join("-")
end
I had the same problem using a date dropdown that wasn't backed by a database attribute. I wrote a little Rack middleware to cope with the problem:
class DateParamsParser
def initialize(app)
#app = app
end
def call(env)
if %w{POST PUT}.include? env['REQUEST_METHOD']
params = Rack::Utils.parse_query(env["rack.input"].read, "&")
# selects only relevant params like 'date1(1i)'
filtered_params = params.select{ |key, value| key =~ /\(\di\)/ }
# delete date params
filtered_params.each { |key, value| params.delete(key) }
# returns something like {'date1' => [2012, 5, 14], 'date2' => [2002, 3, 28]}
date_array_params = filtered_params.sort.reduce({}) do |array_params, keyvalue|
date_key = keyvalue.first.match(/(.+)\(/)[1] + ']'
array_params[date_key] ||= []
array_params[date_key] << keyvalue.last
array_params
end
# Creates params with date strings like {'date1' => '2012-5-14', 'date2' => '2002-3-28'}
date_params = Hash[date_array_params.map{ |key, date_array| [key, date_array.join('-')] }]
params.merge! date_params
env["rack.input"] = StringIO.new(Rack::Utils.build_query(params))
env["rack.input"].rewind
end
#app.call(env)
end
end
And in application.rb I put
config.middleware.insert_before ActionDispatch::ParamsParser, "DateParamsParser"
Note that I only build a date string here. So if you also require time you'll need to build the date_params differently.
I faced the same problem with the model below
class Reservation < ActiveRecord::Base
attr_accessor :sid, :check_in, :credit_card_number, :expiration_date
attr_accessible :expiration_date
end
The corresponding form with the field for the expiration date:
<div class="field">
<%= f.label :expiration_date %>
<%= f.date_select(:expiration_date, start_year: Time.now.year + 3, :end_year => Time.now.year - 3, discard_day: true) %>
</div>
as mentioned by #gabeodess the problem is checking the database to infer the type accordingly the solution I did for it was adding the following code to the model to put the type of the needed attribute in this case :expiration_date so the model is modified to be the following
class Reservation < ActiveRecord::Base
attr_accessor :sid, :check_in, :credit_card_number, :expiration_date
attr_accessible :expiration_date
columns_hash["expiration_date"] = ActiveRecord::ConnectionAdapters::Column.new("expiration_date", nil, "date")
end
Hope this is useful
Remove :include_blank => false from your code.
<%= f.datetime_select(:date_time, :prompt => {:day => 'Day', :month => 'Month', :year => 'Year'}, :start_year => Date.today.year, :end_year => Date.today.year + 2, :minute_step => 15 %>
Thanks....
I was facing the same problem.
I just added attr_accessible for that attribute and it works fine.
Hope it helps.

Rails date_select helper and validation

I have a date field in a model backed form in my Rails App:
<%= f.date_select :birthday,
{:start_year => Time.now.year,
:end_year => 1900,
:use_short_month => true,
:order => [:month, :day, :year],
:prompt => {:month => 'Month', :day => 'Day', :year => 'Year'}},
{:class => 'year',
:id => 'user_birthday'}
%>
It is being validated in the model code using:
validates_presence_of :birthday, :message => 'is a required field'
Unfortunately, if the user enters a partial value such as just the year, the form still submits without an error. Instead a funky date value gets written to the db. How do I make all three fields be mandatory?
I'd like to write a custom validation for this, but I don't know how to properly access the indvidual pieces of the birthday element. How can I do this?
Thanks!
Moe
I think you would have to create the validation in the controller itself.
The date parts are being passed to birthday(1i), birthday(2i) and birthday(3i). The problem here is that they are assigned immediately when passing the attributes and thus before any validations occur.
You could also overwrite the attributes= method to create your own validation there, but I would not suggest you to do that.
Keep in mind that if you do validations, it might be good to validate against any incorrect date as well. (for instance 31st of February, which when passed will yield 2nd of March and not an error).
I think the main issue here is that ActiveRecord is actually replacing the empty values with 1 before creating the Date, which also means that if the visitor pass only the year, the date will be created on the 1st of January that year. I guess that is an expected behaviour to allow use of only one of year/month/day select and still create a useful date.
Related to this post, this is the best solution I've found. However I should add :day, :month, :year as attr_accessible, thing I don't understand why.. (because of validation? please let me know..)
User.rb
MONTHS = ["January", 1], ["February", 2], ...
DAYS = ["01", 1], ["02", 2], ["03", 3], ...
START_YEAR = Time.now.year - 100
END_YEAR = Time.now.year
YEAR_RANGE = START_YEAR..END_YEAR
attr_accessible :day, :month, :year
attr_accessor :day, :month, :year
before_save :prepare_birthday
validate :validate_birthday
private
def prepare_birthday
begin
unless year.blank? # in order to avoid Year like 0000
self.birthday = Date.new(self.year.to_i, self.month.to_i, self.day.to_i)
end
rescue ArgumentError
false
end
end
def validate_birthday
errors.add(:birthday, "Birthday is invalid") unless prepare_birthday
end
user registration form
<%= f.select :month, options_for_select(User::MONTHS), :include_blank => "Month" %>
<%= f.select :day, options_for_select(User::DAYS), :include_blank => "Day" %>
<%= f.select :year, options_for_select(User::YEAR_RANGE), :include_blank =>"Year" %>
You could override the validate_on_create method, like the following:
class MyModel < ActiveRecord::Base
def validate_on_create
Date.parse(birthday)
rescue
errors.add_to_base("Wrong date format")
end
end
After following Benoitr's suggestions I came up with something similar using virtual attributes. On the View side there are 3 separate select's (year,mon,day) inside of a 'fields_for'. The data is submitted to the controller's mass assignment (no modifications in controller, see asciicasts #16) and then passed to a getter/setter (i.e. virtual attribute) in the model. I'm using Rails 3.0.3, and simpleForm for the view code.
In the View:
<%= f.label "Design Date", :class=>"float_left" %>
<%= f.input :design_month, :label => false, :collection => 1..12 %>
<%= f.input :design_day, :label => false, :collection => 1..31 %>
<%= f.input :design_year, :label => false, :collection => 1900..2020 %>
In the Model:
validate :design_date_validator
def design_year
design_date.year
end
def design_month
design_date.month
end
def design_day
design_date.day
end
def design_year=(year)
if year.to_s.blank?
#design_date_errors = true
else
self.design_date = Date.new(year.to_i,design_date.month,design_date.day)
end
end
def design_month=(month)
if month.to_s.blank?
#design_date_errors = true
else
self.design_date = Date.new(design_date.year,month.to_i,design_date.day)
end
end
def design_day=(day)
if day.to_s.blank?
#design_date_errors = true
else
self.design_date = Date.new(design_date.year,design_date.month,day.to_i)
end
end
#validator
def design_date_validator
if #design_date_errors
errors.add(:base, "Design Date Is invalid")
end
end
'design_date_attr' is the virtual attribute which sets the value of design_date in the database. The getter passes back an hash similar to what gets submitted in the form. The setter checks for blanks and creates a new date object and sets it and also sets the error variable. The custom validator :design_date_validator checks for the error instance variable and sets the errors variable. I used ':base' because the variable name was not human readable and using base removes that value from the error string.
A few things to refactor might be the error checking instance variable, but it seems to work at least. If anyone knows a better way to update the Date objects I'd love to hear it.

Resources