How do I change the format ActiveRecord expects when parsing dates from a text field in a form? - ruby-on-rails-3

The problem
I have a Ruby on Rails model with a Date attribute.
In the form for this model, I am using a single text field with a JQuery datepicker to represent this attribute (not a drop down for each of year, month, and day, as is the Rails custom).
The datepicker inserts dates with a mm/dd/yyyy format.
Rails is expecting dates with a dd/mm/yyyy format.
Examples
If a user selects March 12th, 2012, the datepicker puts 03/12/2012, which is interpreted by Rails as December 3rd, 2012.
If a user selects March 20th, 2012, the datepicker puts 03/20/2012, which is interpreted by Rails as the 3rd day of the 20th month of 2012. Since this date doesn't exist, Rails casts this to a nil value (I think).
Question
How do I change the date format Rails uses when parsing this date text field?
Notes:
1) I do not want to change the format of the date the datepicker inserts into the text field,
2) I am not asking about displaying my date attribute in a view.

I initially thought this could be solved through the Rails internationalization features, but it turns out I was wrong.
Ever since Ruby 1.9, the standard format for date parsing is dd/mm/yyyy, so as to better accomodate international users. More details can be found in this SO answer.
That standard is maintained in Rails, as Date.parse is now used to process data from form inputs. Using a before_validation callback won't work because the field is going to be received as nil by the callback method.
Right now there are two gems dealing with this specific issue, namely that date parsing in Rails does not follow the locale settings from I18n.locale. Both seem to work well.
delocalize, by clemens - Seems to have been applied successfully in a decent number or projects and has the highest number of stars at the moment.
i18n_alchemy by carlosantoniodasilva - This one has been released more recently. The author is a Rails core team member, and a very active one at that. Definitely deserves a look.

Since you don't want to change the picker's format, I would suggest you use a hidden field for the actual model property.
For example, add a hidden field for the model's date property, assuming you use a form builder as usual:
f.hidden_field :date
Then for the picker text input, don't bind it to the model's date property. Let's say the hidden field has ID 'modelname_date' and the picker text input has ID 'date_picker', use the following to make it work:
$(function(){
$("#date_picker").datepicker({altField: '#nodelname_date', altFormat: 'dd/mm/yyyy'});
});
In this way the date picker shows the date as 'mm/dd/yyyy' but Rails will see the date as 'dd/mm/yyyy'.
Update:
If you want to work this out on the Rails side, here's another solution I'd suggest:
Add a virtual property to your model: attr_accessor :bad_format_date
Add a before_validation callback in which you parse the input date and assign it to the real field:
before_validation do
self.date = Date.strptime(bad_format_date, "%m/%d/%Y")
end
Then for the form on the view use bad_format_date but initialize it with the date field value (if it's an edit form).

The timeliness gem makes ruby date/time parsing much more customizeable and integrates well with Rails.
Since you're working with Rails, be sure to check out the validates_timeliness project as well by the same guy. It includes all of timeliness plus sophisticated date/time validation methods for ActiveModel.

You could try do something like this.
$(function(){
$('#date_picker').datepicker( {
beforeShowDay: $.datepicker.noWeekends,
showOtherMonths: true,
selectOtherMonths: true,
dateFormat: 'dd-mm-yy',
defaultDate: date,
gotoCurrent: true
});

I just add the following monkey patch to config/time_formats.rb
class Date
class << self
alias :euro_parse :_parse
def _parse(str,comp=false)
str = str.to_s.strip
if str == ''
{}
elsif str =~ /^(\d{1,2})[-\/](\d{1,2})[-\/](\d{2,4})/
year,month,day = $3.to_i,$1,$2
date,*rest = str.split(' ')
year += (year < 35 ? 2000 : 1900) if year < 100
euro_parse("#{year}-#{month}-#{day} #{rest.join(' ')}",comp)
else
euro_parse(str,comp)
end
end
end
end

Related

Rails business hours query for active record

I have two models
OfficeTimeing < ActiveRecord::Base
belongs_to :office
end
Office < ActiveRecord::Base
has_many :office_timings
end
with two fields opening_time and closing_time these fields have string values like "09:00 AM" I want a query how can I find currently open offices I want something like this.
Office.joins(:office_timings).where("Datetime.now > office_timings.opening_time AND office_timings.closing_time > DateTime.now")
I don't know how to compare two times represented as a string without being able to parse them in rails first, however, have you considered storing your opening and closing times in seconds (counting from midnight) rather than strings? That way you would easily be able to write this query.
UPDATE: A few useful methods to achieve this.
To get the current seconds since midnight for a specific time:
seconds = specific_time.seconds_since_midnight
To convert the seconds since midnight into a Time object:
time_instance = Time.at(seconds)
To produce a string in the form of 09:00 AM
time_instance.strftime("%I:%M %p")
Details about Time class.
Details about strftime.
You need to inject Datetime.now into your query using ?:
Office.joins(:office_timings)
.where(
"? > office_timings.opening_time AND office_timings.closing_time > ?",
Datetime.now, Datetime.now
)
Each ? will be safely replaced by the arguments following the query string.
You could also do this directly in Postgres using now(), and make the condition a bit easier to read using BETWEEN:
.where("now() BETWEEN office_timings.opening_time AND office_timings.closing_time")

Time.now.beginning_of_year does not start at the beginning of the year

Trying to get records that were created this year, I stumbled upon this great question. The second answer says you get all records from a model that were created today by saying:
Model.where("created_at >= ?", Time.now.beginning_of_day)
So, naturally, I tried the same thing with Time.now.beginning_of_year, and it works just fine.
However, what struck me as interesting is that the outputted query (I tried it in the console) is
SELECT COUNT(*) FROM `invoices` WHERE (created_at >= '2012-12-31 23:00:00')
I wasn't aware that 2013 already began at 2012-12-31 23:00:00? How's that?
If you haven't set it yet, you should set your timezone in the config/application.rb file. Look for the line that begins with config.time_zone. (If you aren't sure what value to give, you can run rake time:zones:all to get a list of all available timezones.)
Once you've set your timezone, you should use Time.zone.now, as opposed to Time.now. This will properly "scope" your times to your timezone.
Check the API for more details on TimeWithZone.

Rails 3 & i18n: "Object must be a Date, DateTime or Time object" error

I am trying to translate my Rails 3 application, read the primer at http://guides.rubyonrails.org/i18n.html#adding-date-time-formats and subsequently downloaded the corresponding yml file from https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale (de.yml in my case).
In one of my views I had this code (some_model#index) ...
<td><%= time_ago_in_words(some_model.created_at) %></td>
... which I changed in ...
<td><%=l time_ago_in_words(some_model.created_at) %></td>.
Unfortunately this gives me this error:
Object must be a Date, DateTime or Time object. "etwa ein Monat" given.
Any idea why this fails? The created_at column has been created in the database via standard Rails scaffolding (database is mysql using mysql2 gem).
If I strip the time_ago_in_words helper from the code ...
<td><%=l some_model.created_at %></td>.
... the translation works - but the datetime now is of course too long for my <td>.
I also tried to duplicated the distance_in_words section of the de.yml and rename it to time_ago_in_words but this did not work either.
Am I missing something obvious?
OK, there are 2 different methods in play here :
the l method takes a Date, a Time or a DateTime object and returns a formatted version based on your I18n rules.
the time_ago_words takes the same arguments and uses I18n to spit out a formatted string.
In your example, you're trying to use both! Put simply, all you need is <%= time_ago_in_words(some_model.created_at) %>.

Rails 3 jquery date picker date not saving to database

When I submit my form I can see the date being sent to in the post. However, It doesn't save the date. If I do a date check it says it is not in the proper format. Here is my date picker function, it displays fine:
$j(function(){
$j("#mile_date").datepicker();
});
the $j is because I am using prototype as well so all jquery calls are using the the noconflict variable.
Here is the post:
Parameters: {"utf8"=>"Γ£ô", "authenticity_token"=>"Fj4HW/B4EOan/vcPZLJ75TvWkRH4ZKSFsPLlQLSD0cI=", "mile"=>{"odometer"=>"", "trip"=>"428.2
", "gallons"=>"24.959", "note"=>"", "temperature"=>"", "date"=>"06/22/2011"}, "commit"=>"Create Mile"}
So it sends the date fine but rails doesn't seem to like the format. It inserts a null value into the database. If I submit it with the default datefield with the drop downs it sends this and saves fine:
Parameters: {"utf8"=>"Γ£ô", "authenticity_token"=>"Fj4HW/B4EOan/vcPZLJ75TvWkRH4ZKSFsPLlQLSD0cI=", "mile"=>{"odometer"=>"", "trip"=>"428.2
", "gallons"=>"24.959", "mpg"=>"17.156136063144", "note"=>"", "temperature"=>"", "date(1i)"=>"2011", "date(2i)"=>"6", "date(3i)"=>"22"}, "c
ommit"=>"Create Mile"}
In the insert statement it inserts the date as:'2011-06-22'
Does Rails expect the date in 3 variables to construct the date format correctly? How can I get the datepicker to send the correct date format?
Thank you in advance,
I ran into the same issue. One solution is to simply change the format that the datepicker uses:
// in your javascript...
$j(function(){
$j("#mile_date").datepicker({
dateFormat: "yy-mm-dd"
});
});
Rails seems to be able to handle the yy-mm-dd format - I'm using that and am having no issues saving the date to the database. The only issue here is that some might find the yy-mm-dd format a little less good looking than mm/dd/yyyy...
As noted by #BaronVonBraun above rails doesn't seem to handle that format. Changing it as he suggested worked. However, for those wanting a different format than yy-mm-dd you can use the following. The user sees the format you want while rails gets the format it needs.
$j(function(){
$j("#show_date").datepicker({altField: '#mile_date', altFormat: 'yy-mm-dd'});
});
The show_date is the id of the field they see and the mile_date is a hidden field with the date rails needs.
Here is the documentation.
If anyone is using the jquery_datepicker gem , you'll want to use something similar to the following code in your rails view.
<%= form.hidden_field(:ship_date, :id => "ship_date") %>
<%= datepicker_input(:show_date, item.id, :size => 10, altField: "#ship_date", altFormat: 'yy-mm-dd', :value => item.ship_date.strftime("%m/%d/%Y"))%>
You can also use form.datepicker_input to attach the the date picker directly to the form, but in my use case, I wanted the date picker to reflect the localized date, which Rails would not accept. So I added a hidden form element and set the alternate field to it, works perfectly!
Or try the delocalize gem: https://github.com/clemens/delocalize
j('.jquery-calendar').datepicker().each(function(){
// convert dates from db to US format
var $input = j(this)
var found = $input.val().match(/^(\d{4})-(\d{2})-(\d{2})$/);
if(found){
$input.val(found[2]+'/'+found[3]+'/'+found[1]);
}
});
Kinda of hacky but made a helper the set things straight when I know I am giving the server dates in the mm-dd-yy format
def convert_to_y_m_d(date)
new_date = date.split("-")[2] + "-" + date.split("-")[0] + "-" + date.split("-")[1]
new_date
end

How to make assigning an american formatted date string into a Date variable work in Rails 3

My users input american formatted dates (mm/dd/yyyy). When the model is saved the date attributes turn to nil:
my_model.start_date = "07/30/2011"
puts my_model.start_date
nil
I get an invalid date error if I do this in the console:
Date.parse("07/30/2011")
How do you force the app to accept american date format? I know the app should handle localization correctly but a patch is what I need for the short term.
I found a gem that does the same thing and all you have to do is put this in your gem file:
gem 'american_date'
I know this is a sort of old post, but i figured this may help other people and is a little easier than the monkey patch.
After much digging around I found this (posted by Troyk in git https://gist.github.com/922048)
# Date.parse() with Ruby 1.9 is now defaulting to the European date style where the format is DD/MM/YYYY, not MM/DD/YYYY
# patch it to use US format by default
class Date
class << self
alias :euro_parse :_parse
def _parse(str,comp=false)
str = str.to_s.strip
if str == ''
nil
elsif str =~ /^(\d{1,2})[-\/](\d{1,2})[-\/](\d{2,4})/
year,month,day = $3.to_i,$1,$2
date,*rest = str.split(' ')
year += (year < 35 ? 2000 : 1900) if year < 100
euro_parse("#{year}-#{month}-#{day} #{rest.join(' ')}",comp)
else
euro_parse(str,comp)
end
end
end
end
Stick this in config/initializiers/american_date_monkey_patch.rb and you are all set.
If your users are not all American then this solution is not for you.