codeetcetera
codeetcetera

Reputation: 3203

How to parse a date without a year in Ruby

I'm trying to parse a date in ruby on rails so I can get the month and day out of it.

 DateTime.parse("07/09") // Works
 DateTime.parse("02/07/1975") // Works
 DateTime.parse("08/26/1983") // Fails 
 DateTime.parse("04/28/1982") // Fails
 DateTime.parse("10/17/1961") // Fails

Why would that one fail? There are several others that fail too, with no common thread that I can see.

Upvotes: 2

Views: 5460

Answers (4)

the Tin Man
the Tin Man

Reputation: 160551

One of the important things about parsing dates is to be in control. Ruby's designers made a good decision to assume 12/1/2000 is in "DD/MM/YYYY" format, because it's for the common good, it just rubs us 'Mericans wrong, but, then again, we have weird rulers, spell "color" funny, and drive on the wrong side of the road too.

Because we know our dates are in a weird format we can use Date.strptime or DateTime.strptime or Time.strptime to specify the actual format the date is in. Use '%m/%d/%Y' for leading months, or '%d/%m/%Y' for the rest of us... them... whichever.

Date, DateTime and Time have the parse method also, which is able to handle a lot of different formats, but will choke on that one month vs. day issue. I use them as starting points in code if I am confident I won't have a collision with 'Merican vs. everyone-else dates.

If you don't know your date data's source, one tactic is to start a parse of the file using '%m/%d' format, and rescue the parsing error. If you encounter one, which is kind-of likely, rewind the file and retry the load using '%d/%m' format.

Or... for true flexibility, look at the almost-insanely awesome Chronic gem. It will still trip over the %m/%d vs. %d/%m issue, but that can't be helped.

There is no way software can make an good decision over the date format, even by looking at LOCALE settings or knowing the longitude/latitude coordinates of the incoming data because data can come from anywhere.

Upvotes: 0

Todd A. Jacobs
Todd A. Jacobs

Reputation: 84343

Problem

Your problem stems from the fact that Ruby has several date- and time-related classes. These include:

Each of the classes varies in its capabilities and implementation details, but only Time will even parse your particular string. For example:

Date.new "08/26/1983"
# => ArgumentError: comparison of String with 0 failed

DateTime.new "08/26/1983"
# => ArgumentError: comparison of String with 0 failed

Time.new "08/26/1983"
# => 0008-01-01 00:00:00 -0500

Obviously, it is not the year 0008 A.D., so you need to pass a structured date that your selected date class understands.

Solution

You need to specify the format of your date string using #strptime. For example:

Date.strptime('08/26/1983', '%m/%d/%Y').to_s
# => "1983-08-26"

DateTime.strptime('08/26/1983', '%m/%d/%Y').to_s
# => "1983-08-26T00:00:00+00:00"

you can then use formatting methods provided by your chosen class, such as DateTime#strftime or DateTime#rfc2822 to format the output in whatever way your application requires.

Use GNU Date Instead

If you have access to GNU date, you can take advantage of its more liberal parsing. You may still run into issues with locale, but in general GNU date is pretty robust. For example:

%x(date -d 1983/08/26).chomp
# => "Fri Aug 26 00:00:00 EDT 1983"

%x(date -d 08/26/1983).chomp
=> "Fri Aug 26 00:00:00 EDT 1983"

I don't think GNU date accepts input dates in European order (e.g. dd/mm/yyyy) but will certainly honor locale in the output. That shouldn't matter for your use case, but seems worth mentioning for future visitors.

Upvotes: 0

Amit Kumar Gupta
Amit Kumar Gupta

Reputation: 18567

Because DateTime.parse("xx/xx/xxxx") assumes you're giving it the format DateTime("dd/mm/yyyy"), i.e. the way that makes sense to most of the world outside the US. The last three examples don't work because there isn't a 26th, 28th, or 17th month. Notice that your second example works, but it probably doesn't give you what you think:

 DateTime.parse("02/07/1975").month # => 7

Instead of parse, use strptime so you can be explicit about the format from which to parse the date:

DateTime.strptime("02/07/1975", "%m/%d/%Y")
DateTime.strptime("02/07/1975", "%m/%d/%Y").month # => 2

Upvotes: 3

DWilches
DWilches

Reputation: 23016

Try strptime in DateTime. It receives a format string.

Documentation on ruby-doc.org

Upvotes: 2

Related Questions