0

On rubular I tested and confirmed that this does a good job confirming the desired format of a date entry:

\A\d\d\/\d\d\/\d\d\d\d\z

Tests:

01/02/2000 #pass
11/21/2014 #pass
11-21-2014 #fail
A3-21-2014 #fail

I want to make it a little bit better, and it will be good enough for me. What I want is to confirm that the "month field" (the first two digits) is anywhere from 01 - 12, where each single digit is led by a zero. (Ex: 01,02,03 etc as opposed to: 1,2,3).

Next: I want to do the same thing for the next two digits to confirm that the next two digits (the day field) is between 01 - 31. Same thing: Each single digit needs to lead with a zero.

Tests:

01/02/2017 #pass
12/31/2017 #pass
1/02/2017  #fail
01/2/2017  #fail
50/01/2017 #fail
01/50/2017 #fail

I realize that this regex will be inaccurate for those months that have fewer than 31 days, but it is good enough for what I am doing.

Neil
  • 4,578
  • 14
  • 70
  • 155
  • It seems like it would be less work _and_ more accurate to just use `Date.parse()` on the input string and catch the exception thrown by invalid dates. You could then do a string comparison against the formatted output from the parsed `Date` object to make sure it matched your input format. – Michael Berkowski May 05 '17 at 17:24
  • `Date.parse` will fail if the date isn't in `%d/%m/%y` format for certain days of each month if presented with U.S. formatted dates in `%m/%d/%y` format. (The OP's regex doesn't check for that). `parse` is also slower than using explicit DateTime/Time format strings. – the Tin Man May 05 '17 at 17:27
  • 1
    Don't try to do it with a regex. While it's possible, it'll result in a complicated pattern that could easily be replaced with a bit of smart code. We'd like to see your attempt to solve this as currently it looks like you want us to write a solution for you. – the Tin Man May 05 '17 at 17:29
  • So how then do I validate a date in an american date format, "mm/dd/yyyy", without regex? This is what I have: `validates_format_of :date_of_birth, :with => /\A\d\d\/\d\d\/\d\d\d\d\z/, allow_blank: true, message: "Date of Birth Format Invalid: mm/dd/yyyy"` – Neil May 05 '17 at 17:31
  • 1
    If you know your dates are _ALWAYS_ in U.S. format, then simply use `strptime` to parse it using a `%m/%d/%Y` format string. It'll raise an exception if the value doesn't fit. A regular expression is good for looking at the structure, but they're not good for seeing if values fit within a range. It _CAN_ be done but there are easier ways of seeing if the values are valid. – the Tin Man May 05 '17 at 17:34
  • Just to clarify: `Date.parse` doesn't know the different between dates in `%m/%d/%y` format and those in `%d/%m/%y` format, nor can it determine which to use, so, because `%d/%m/%y` is more prevalent it uses that. Passing in U.S. dates can result in Date trying to create months > 12, and it'll complain. `Date.parse('1/31/2001') # ~> ArgumentError: invalid date` – the Tin Man May 05 '17 at 17:37
  • Why the down vote? Isn't this question useful and shows an honest attempt? – Neil May 05 '17 at 17:39
  • 2
    Why is this even a validation? Shouldn't `date_of_birth` have a `date` type in the database? Then the model can require incoming dates to be in ISO8601 format and the controller can clean things up if you want to allow other formats. – mu is too short May 05 '17 at 17:43
  • Hover over the up and down vote buttons to see what they mean, unfortunately they get used for disproval or approval for a variety of other reasons too. Dealing with dates is not a trivial problem and any time a user can enter the date the odds are really good they'll get it wrong. A date picker is essential in a GUI. If you're getting data as a file then you have to check to see if the sender is using the right format; It can be very difficult when dealing with multiple sources of the data. – the Tin Man May 06 '17 at 00:32

2 Answers2

1

What I did was used the american_date gem. On your date inputs: the user should enter the date in the format of: "mm/dd/yyyy".

In order to force the user to enter the date in this format: I used jquery-inputmask-rails. I defined my mask like so:

$('.mask_american_date').inputmask({mask: "99/99/9999"});

Now there will be a nice mask on the date input that looks like this:

__/__/____

Now: all you need is a presence validator for the date field in your model:

validates_presence_of :date_of_birth, message: "Date is either invalid or is not present".

And this covers everything. How american date works is it takes the user input and attempts to convert it into a date. If it cannot convert the user input into a date for any reason: it will return nil which triggers the above validation.

  • This includes a bad month entry or a bad day entry. American Date is smart enough to know, for example, that September only has 30 days in it. So: if the user enters "31" for the day section, ex: 09/31/2017, american date will convert the date to nil.
miken32
  • 42,008
  • 16
  • 111
  • 154
Neil
  • 4,578
  • 14
  • 70
  • 155
  • This won't fix the problem if someone from a different part of the world enters a date in DD/MM/YYYY format, it'll just tell them it's invalid and will frustrate them. You should ask them their locale and adjust based on that, or use a date picker which will not allow them to enter invalid information. – the Tin Man May 06 '17 at 00:36
  • @theTinMan it was good enough for me. I am aware that the american_date gem only works for america. I'm only dealing with the US so it is good enough. Perhaps you could provide a solution then? – Neil May 07 '17 at 01:00
  • There is no solution unless you know, beyond all doubt, that the dates are from the U.S. If you know that, then it's a trivial problem, solved simply by using `strptime('some_mm/dd/yyyy', '%m/%d/%Y')`. No gem is needed because Date knows how many days are in each month. – the Tin Man May 08 '17 at 17:57
  • http://stackoverflow.com/a/5355056/128421 goes further into the problem. – the Tin Man May 08 '17 at 22:49
1

Well this should get you most of the way there:

/((02\/[0-2]\d)|((01|[0][3-9]|[1][0-2])\/(31|30|[0-2]\d)))\/[12]\d{3}/

Granted it does not handle the following:

  • Leap Years e.g. 02/29 is acceptable regardless of the year
  • All Years from 1000-2999 are acceptable
  • Months with only 30 days e.g. 09/31 is acceptable

Small Breakdown in case links break:

Here is the runout on Rubular Here is an explanation from Regex101

  • (02\/[0-2]\d) - Starts with 02/ then allow 0-2 followed by 0-9
  • OR ((01|[0][3-9]|[1][0-2]\/(31|30|[0-2]\d)) - Starts with (01 or 0 followed by 3-9 or 1 followed by 0-2) followed a / followed by 31 or 30 or 0-2 followed by 0-9
  • In both cases must be followed by 1 or 2 followed by 3 digits 0-9

Really wish ruby supported look behind conditionals like true pcre Example for edification

As a Note: as mentioned in the comments rescuing a parsing failure is probably easier than using a regex but I figured since you asked.

engineersmnky
  • 25,495
  • 2
  • 36
  • 52
  • [Close only counts in horseshoes and grenades.](http://idioms.thefreedictionary.com/Close+only+counts+in+horseshoes). – Cary Swoveland May 06 '17 at 19:21
  • 1
    @caryswoveland technically I succeeded *"I want to make it a little bit better, and it will be good enough for me."* Although I'd call it more a horseshoe than a hand grenade. Close also counts in bocce and I'd take close in the lottery as well ;) – engineersmnky May 06 '17 at 19:39