I've run into a spot of bother with date formats in our Rails application.
I have a date field in our view which I want to be formatted as dd/mm/yy. This is how the user will expect to enter their dates, and the datepicker control uses this format.
However, Active Record seems to be expecting mm/dd/yy.
If I enter 01/03/2010, this gets put in as 03 January 2010.
If I enter 25/03/2010, this gets put in a null.
How do I get ActiveRecord to expect Her Majesties date format?
Rails' DateTime tries to detect the formatting automatically. It will detect the following formats: mm/dd/yy or dd-mm-yy or yyyy-mm-dd or yyyy/mm/dd. You could monkey-patch DateTime.parse, but I would rather move this issue to the View of your application.
I always recommend to use yyyy-mm-dd [hh:mm:ss] as a string representation for a date. Check the documentation of your DatePicker if it supports multiple date-formats.
The jQuery date-picker for example has this covered with dateFormat (for the data that is sent to the server, set this to yyyy-mm-dd) as well as altFormat (for the input the user sees, set this to dd/mm/yyyy).
Add a file called rails_defaults.rb to config\initializers directory; with following lines:
Date::DATE_FORMATS[:default] = '%d/%m/%Y'
Time::DATE_FORMATS[:default]= '%d/%m/%Y %H:%M:%S'
Restart the server and you are good to go.
class Date
class << self
def _parse_with_us_format(date, *args)
if date =~ %r{^(\d+)/(\d+)/(\d+)$}
_parse_without_us_format("#{$3.length == 2 ? "20#{$3}" : $3}-#{$1}-#{$2}", *args)
else
_parse_without_us_format(date, *args)
end
end
alias_method_chain :_parse, :us_format
end
end
Related
I need to check if a DateTime is in a valid ISO8601 format.
Like: #iso8601?
I checked if ruby has a specific method but I haven't found any.
Currently I'm using date.iso8601 == date to check this.
Is there a good way to do this?
EDIT
Explaining my environment, and changing the scope of the question.
So, my project will use the js api FullCalendar, that's why i need a iso8601 string format. And I wondered what it's better or the correct way, save the date in the database in the correct format, or let the ActiveRecord do their job and manipulate it on just when I require the time information.
I dont' quite understand your question. I am assuming that you want to check a DateTime string if it's a valid ISO8601 format date string or not. Correct me if I am wrong.
You can use the class methods Time.iso8601 and Date.iso8601. In order to use them, you need to require the 'date' and 'time' library from standard library. One caveat is, as you can see from the name, they are not predicate method (without ?), instead they raise an ArgumentError with "invalid date" message if the wrong string is being input. So, you need to add a begin rescue block. eg.
require 'time'
t = Time.now
time_string = t.rfc2822 # intentionally set to wrong format string
begin
Time.iso8601(time_string)
puts "Yeah, correct string"
rescue ArgumentError => e
puts e
puts "Nah, wrong string"
end
This is more verbose than your "date.iso8601 == date". But I am posting it because don't understand how your method works. To me date.iso8601 == date would always be false. Am I wrong?
UPDATE
As an answer for your updated question, it's best you can just store the DateTime normally in the database and call iso8601 method to get the ISO8601 string. For creating DateTime, you can just use Time.iso8601 to parse the input string into DateTime object.
Ruby 3.1:
begin
invalid_date = '1970-01-32'
Date.iso8601(invalid_date)
rescue Date::Error
puts 'The date is invalid'
end
Rails 6.1:
Given the attribute has the type of date in the underlying database, you will not be able to set it to the invalid value. The following test (written in RSpec) passes:
specify 'Rails ignores invalid date' do
invalid_date = '1970-01-32'
invoice = Invoice.new
invoice.date = invalid_date
expect(invoice.date).to be nil
end
P.S. It's not fully clear whether you needed to make it work with Rails or without so there are solutions for both cases.
I do have a model within a Rails application with the following definition (I am using Mongoid):
field :date, type: Date
I included the field within the view as a text_field (f.text_field). Everything works fine, if I do enter a valid date. But when I enter some text in this field (e.g. 'foo'), then the date gets parsed as 01.01.1970. I am not parsing the date manually, rather I use the following code:
#offer = Offer.new(offer_params)
I also checked on the Ruby console:
>> offer = Offer.new
...
>> offer.date = 'foo'
"foo"
>> offer.date
Thu, 01 Jan 1970
>> offer.date.is_a? Date
true
How can I prevent this behavior so that the record can't be saved and a validation error gets shown to the user?
Thanks for your help!
You can modify the Offer initializer so that it will use the Date::parse method for parsing your date. This will throw an ArgumentError, when the parameter you supply to it is not a valid date format.
You may have to convert that to seconds since 1.1.1970 as that seems to be the format used for storing your dates in your database. Have a look at Date#strftime for that.
I'm in the U.S., and we usually format dates as "month/day/year". I'm trying to make sure that my Rails app, using Ruby 1.9, assumes this format everywhere, and works the way it did under Ruby 1.8.
I know that lots of people have this issue, so I'd like to create a definitive guide here.
Specifically:
'04/01/2011' is April 1, 2011, not Jan 4, 2011.
'4/1/2011' is also April 1, 2011 - the leading zeros should not be necessary.
How can I do this?
Here's what I have so far.
Controlling Date#to_s behavior
I have this line in application.rb:
# Format our dates like "12/25/2011'
Date::DATE_FORMATS[:default] = '%m/%d/%Y'
This ensures that if I do the following:
d = Date.new(2011,4,1)
d.to_s
... I get "04/01/2011", not "2011-04-01".
Controlling String#to_date behavior
ActiveSupport's String#to_date method currently looks like this (source):
def to_date
return nil if self.blank?
::Date.new(*::Date._parse(self, false).values_at(:year, :mon, :mday))
end
(In case you don't follow that, the second line creates a new date, passing in year, month and day, in that order. The way it gets the year, month and day values is by using Date._parse, which parses a string and somehow decides what those values are, then returns a hash. .values_at pulls the values out of that hash in the order Date.new wants them.)
Since I know that I will normally pass in strings like "04/01/2011" or "4/1/2011", I can fix this by monkeypatching it like this:
class String
# Keep a pointer to ActiveSupport's String#to_date
alias_method :old_to_date, :to_date
# Redefine it as follows
def to_date
return nil if self.blank?
begin
# Start by assuming the values are in this order, separated by /
month, day, year = self.split('/').map(&:to_i)
::Date.new(year, month, day)
rescue
# If this fails - like for "April 4, 2011" - fall back to original behavior
begin
old_to_date
rescue NoMethodError => e
# Stupid, unhelpful error from the bowels of Ruby date-parsing code
if e.message == "undefined method `<' for nil:NilClass"
raise InvalidDateError.new("#{self} is not a valid date")
else
raise e
end
end
end
end
end
class InvalidDateError < StandardError; end;
This solution makes my tests pass, but is it crazy? Am I just missing a configuration option somewhere, or is there some other, easier solution?
Are there any other date-parsing cases I'm not covering?
Gem: ruby-american_date
This gem was created since I asked this question. I'm now using it and have been pleased.
https://github.com/jeremyevans/ruby-american_date
Date.strptime is probably what you're looking for in ruby 1.9.
You're probably stuck monkeypatching it onto string.to_date for now, but strptime is the best solution for parsing dates from strings in ruby 1.9.
Also, the formats are symmetric with strftime as far as I know.
you can use rails-i18n gem or just copy the en-US.yml and set your default locale "en-US" in config/application.rb
For parsing US-style dates, you could use:
Date.strptime(date_string, '%m/%d/%Y')
In console:
> Date.strptime('04/01/2011', '%m/%d/%Y')
=> Fri, 01 Apr 2011
> Date.strptime('4/1/2011', '%m/%d/%Y')
=> Fri, 01 Apr 2011
Use REE? :D
Seriously though. If this is a small app you have complete control over or you are standardizing on that date format, monkey patching for a project is totally reasonable. You just need to make sure all your inputs come in with the correct format, be it via API or website.
Instead of using to_s for Date instances, get in the habit of using strftime. It takes a format string that gives you complete control over the date format.
Edit:
strptime gives you full control over the parsing by specifying a format string as well. You can use the same format string in both methods.
Another option is Chronic - http://chronic.rubyforge.org/
You just need to set the endian preference to force only MM/DD/YYYY date format:
Chronic::DEFAULT_OPTIONS[ :endian_precedence ] = [ :middle ]
However the default for Chronic is the out-of-order US date format anyway!
I'm using rails 3 and I want to change the default date format of created_at and updated_at date when I save them into the db.
Default date format is %Y-%m-%d %H:%M:%S, but
I would like it to be %Y%m%d%H%M%S
Where should I change the format? I'm trying to create a time_formats.rb in the initializer folder.
Here is content:
class TimeFormats
Time::DATE_FORMATS[:db] = "%Y%m%d%H%M%S"
Time::DATE_FORMATS[:default] = "%Y%m%d%H%M%S"
end
This does not work. Is there someone who can help me? Thank you.
The time and date formats that you define in the initializers apply only when converting them to strings in Ruby. The formats you've defined would be used like Time.now.to_s(:default).
I don't recommend (nor am I aware of a way how) to change how dates are stored in the database. You should let the database store them as it does by default, then change how they are formatted in the views using .to_s(:format) as defined in the initializers.
Normally the database stores the timestamps as a specific timedate datatype rather than a formatted string. If you just want default timestamp printing style to be different, you might try overriding the created_at and updated_at methods:
def updated_at
super.strftime("%Y%m%d%H%M%S")
end
For instance, if I have a Time.now, how would I convert that to a friendly url string?
Time.now.some_method_here => some_url_friendly_string_here
I believe there is a built-in Ruby method to do so, but I can't seem to locate it on Google. Any ideas?
As you're using Rails (as indicated by your tag), you can use .to_s:
Time.now.to_s
You can specify a time format to the method, which will format the string differently, such as Time.now.to_s(:db). Please see this link for the default date formats. If you don't specify a format, it'll use the same as strftime.
The next part of that page also describes how to add your own time formats, which is very simple:
# Initializer
Time::DATE_FORMATS[:month_and_year] = "%B %Y"
# Any code
Time.now.to_s(:month_and_year) # => September 2010
Everything in ruby is open and extensible
class Time
def to_url_string
self.strftime("%H:%M:%s")
end
end
You can use the strftime method. It's a bit cryptic, but if you look at the docs, you can pick out the year, month, day, etc.
For example, Time.now.strftime("%H:%M:%s") will give the time in hours, minutes and seconds.
You might consider using the xmlschema method which will return a date/time in the format:
CCYY-MM-DDThh:mm:ssTZD
or
CCYY-MM-DDThh:mm:ss.sssTZD
depending on the value of the fraction_digits argument.
This would give you a time like:
2010-09-15T20:31:15+05:00
for example.
See the docs for more info and a link to the code.