I'm currently working through the Well-Grounded Rubyist and have a question about an exercise that is asking to check whether a date provided is in the format 'yyyy-mm-dd' as opposed to 'yy-mm-dd'.
We have a class Ticket and should create a date= method that checks whether the date provided is in the above mentioned format.
Is .strftime correct to use here?
In the end the method should return the date in the correct format and provide an error message for dates in the wrong format, like so:
ticket = Ticket.new
ticket.date = "2013-11-12"
=> "2013-11-12"
ticket.date = "13-11-12"
=> "Please submit the date in the format 'yyyy-mm-dd'."
Could someone indicate how I could perform these checks on dates?
Date::xmlschema is strict about this specific format (try this in IRB):
require 'date'
Date.xmlschema("2013-11-12") #<Date: 2013-11-12 ((2456609j,0s,0n),+0s,2299161j)>
#invalid month number:
Date.xmlschema("2013-13-12") #<ArgumentError: invalid date>
# 2 digit year:
Date.xmlschema("13-11-12") #<ArgumentError: invalid date>
# no leap year:
Date.xmlschema("2013-02-29") #<ArgumentError: invalid date>
You can throw the error to the user by using begin..rescue
require 'date'
begin
Date.parse("31-02-2010")
rescue => e
p "#{e}" #print your own custom messages and return accordingly
end
Also write
rescue ArgumentError
It will throw By default error
ArgumentError (invalid date)
I am dynamically updating the underlying model by passing the parameters from the posted form to the model like this:
#model.assign_attributes(params[:model])
I have a date coming in along with the rest of the post data in the format mm/dd/yyyy. Ruby appears to be parsing this date as if it is in the format dd/mm/yyyy, as far as I can tell. So, when I enter a date of 4/15/2014, for instance, the model fails to save because the month 15 does not exist (I assume, in reality I am just told that the date field is required).
How can I configure Ruby to parse my dates as mm/dd/yyyy?
Use the strptime method for date and supply the format.
Date.strptime("4/15/2014","%m/%d/%Y")
#=> #<Date: 2014-04-15 ((2456763j,0s,0n),+0s,2299161j)>
You will probably have to specify a call back like before_update if you want the conversion to happen in the model.
e.g.
class YourModel < ActiveRecord::Base
before_update :format_date
def format_date
date_field = Date.strptime(date_field,'%m/%d/%Y')
end
end
I search a solution for validate user birth date, for example if user try to submit only day and month but he doesn't select year, date will be invalid, but rails don't show any exception or error if year is not set for example but he skip date, and date will not be updated
I found some old answers about the subject, and it seem not to be clear for me. (note i use date_select helper in my view)
I did already a search about date validation but I'm looking for an effective solution that is up to date
i will be thankful for any suggestion. thank you
DateTime.parse(..) will raise an error if the date is not valid:
[1] pry(main)> DateTime.parse("Feb 31, 2013")
ArgumentError: invalid date
from (pry):1:in `parse'
[2] pry(main)> e = begin; DateTime.parse("Feb 31, 2013"); rescue => e; e; end
=> #<ArgumentError: invalid date>
So just capture the error to check for a valid date:
def valid_date?(string)
begin
DateTime.parse(string)
return true
rescue => e
return false
end
end
I have two date parameters in a controller action that I would like to fall-back to a default value if they are nil, or parsing fails.
Unfortunately, it seems that DateTime.strptime throws an exception if parsing fails, which forces me to write this monstrosity:
starting = if params[:starting].present?
begin
DateTime.strptime(params[:starting], "%Y-%m-%d")
rescue
#meeting_range.first
end
else
#meeting_range.first
end
Feels bad man. Is there any way to parse a date with the Ruby stdlib that doesn't require a begin...rescue block? Chronic feels like overkill for this situation.
In general, I can't agree with the other solution, using rescue in this way is bad practice. I think it's worth mentioning in case someone else tries to apply the concept to a different implementation.
My concern is that some other exception you might be interested in will be hidden by that rescue, breaking the early error detection rule.
The following is for Date not DateTime but you'll get the idea:
Date.parse(home.build_time) # where build_time does not exist or home is nil
Date.parse(calculated_time) # with any exception in calculated_time
Having to face the same problem I ended up monkey patching Ruby as follows:
# date.rb
class Date
def self.safe_parse(value, default = nil)
Date.parse(value.to_s)
rescue ArgumentError
default
end
end
Any exception in value will be rose before entering the method, and only ArgumentError is caught (although I'm not aware of any other possible ones).
The only proper use of inline rescue is something similar to this:
f(x) rescue handle($!)
Update
These days I prefer to not monkey patch Ruby. Instead, I wrap my Date in a Rich module, which I put in lib/rich, I then call it with:
Rich::Date.safe_parse(date)
Why not simply:
starting = DateTime.strptime(params[:starting], '%Y-%m-%d') rescue #meeting_range.first
My preferred approach these days is to use Dry::Types for type coercions and Dry::Monads for representing errors.
require "dry/types"
require "dry/monads"
Dry::Types.load_extensions(:monads)
Types = Dry::Types(default: :strict)
Types::Date.try("2021-07-27T12:23:19-05:00")
# => Success(Tue, 27 Jul 2021)
Types::Date.try("foo")
# => Failure(ConstraintError: "foo" violates constraints (type?(Date, "foo"))
All of the existing answers do have rescue somewhere. However, we can use some "ugly" methods that was available from Ruby version 1.9.3 (it was there before but there is no official description).
The method is ugly because it starts with an underscore. However, it fits the purpose.
With this, the method call in the question can be written
starting = if params[:starting].present?
parsed = DateTime._strptime(params[:starting], "%Y-%m-%d") || {}
if parsed.count==3 && Date.valid_date?(parsed[:year], parsed[:month], parsed[:mday])
#meeting_range.first
end
else
#meeting_range.first
end
If the date string is matching the input format, _strptime will return a hash with all 3 date parts. so parsed.count==3 means all 3 parts exists.
However a further check that three parts forms a valid date in the calendar is still necessary since _strptime will not tell you they are not valid.
When you would to get date as object, parsed from string variable, sometimes passed string value may be nil, or empty, or invalid date string. I'd like to wrote safe metods for short:
def safe_date(string_date)
::Date.parse(string_date)
rescue TypeError, ::Date::Error
::Date.today
end
For example - check in irb console:
3.0.2 :001 > safe_date
=> #<Date: 2022-08-29 ((2459821j,0s,0n),+0s,2299161j)>
3.0.2 :001 > safe_date('')
=> #<Date: 2022-08-29 ((2459821j,0s,0n),+0s,2299161j)>
3.0.2 :002 > safe_date('29.12.2022')
=> #<Date: 2022-12-29 ((2459943j,0s,0n),+0s,2299161j)>
3.0.2 :003 > safe_date('29.13.2022')
=> #<Date: 2022-08-29 ((2459821j,0s,0n),+0s,2299161j)>
I write my first project wich using Datamapper as ORM, so please be patient. :)
I try to do get String from DateTime field:
Error.first.submitted_at.to_s
=> "2009-08-24T12:13:32+02:00"
Returned String is not good for me. In ActiveRecord I can do something like that:
Error.first.submitted_at.to_s(:only_date)
or any other date formatter. Is somethig similar available in DataMapper or I must to use strftime method?
That's a feature available using AcitveSupport. You can do require 'activesupport' to get it. That might be overkill, though. You could also use #stamp from Facets to do the same thing, but you have to set up the :only_date format:
require 'facets/date'
Date::FORMAT[:only_date] = '%d.%m.%y' # For Date objects
Time::FORMAT[:only_date] = '%d.%m.%y' # For DateTime objects
d = DateTime.now
d.stamp(:only_date) # => "24.08.09"
If you really want to use it with the to_s method, you can do that, too:
require 'facets/date'
Date::FORMAT[:only_date] = '%d.%m.%y' # For Date objects
Time::FORMAT[:only_date] = '%d.%m.%y' # For DateTime objects
class DateTime
alias :default_to_s :to_s
def to_s(format=nil)
if format.nil?
default_to_s
else
stamp format
end
end
end
d = DateTime.now
d.to_s(:only_date) # => "24.08.09"