1

I need a regex pattern which matches a date with optional time.

The date should be a valid U.S. date in m/d/yyyy format. The time should be h:mm:ss am/pm or 24-hour time hh:mm:ss.

Matches: 9/1/2011 | 9/1/2011 10:00 am | 9/1/2011 10:00 AM | 9/1/2011 10:00:00

This pattern will be used in a Ruby on Rails project, so it should be in a format usable via Ruby. See http://rubular.com/ for testing.

Here's my existing date pattern (which may be an over-kill):

DATE_PATTERN = /^((((0[13578])|([13578])|(1[02]))[\/](([1-9])|([0-2][0-9])|(3[01])))|(((0[469])|([469])|(11))[\/](([1-9])|([0-2][0-9])|(30)))|((2|02)[\/](([1-9])|([0-2][0-9]))))[\/]\d{4}$|^\d{4}/
Edward J. Stembler
  • 1,932
  • 4
  • 30
  • 53

4 Answers4

9

Regular expressions are horrible for this kind of job. If you're using Ruby I'd recommend using DateTime.strptime to parse the data and check its validity:

def validate_date(date_str)
  valid_formats = ["%m/%d/%Y", "%m/%d/%Y %I:%M %P"] 
  #see http://www.ruby-doc.org/core-1.9.3/Time.html#method-i-strftime for more

  valid_formats.each do |format|
    valid = Time.strptime(date_str, format) rescue false

    return true if valid
  end

  return false
end
Alex Peattie
  • 26,633
  • 5
  • 50
  • 52
  • Well, your approach seems like a valid alternative. However, I'm dealing with existing code which is setup to use regex patterns. A custom DSL based on: [Create Your Own Programming Language](http://createyourproglang.com) – Edward J. Stembler Nov 16 '11 at 18:46
2

Well, here's what I ended up with; using stricter military time:

DATE_TIME_FORMAT = /^([0,1]?\d{1})\/([0-2]?\d{1}|[3][0,1]{1})\/([1]{1}[9]{1}[9]{1}\d{1}|[2-9]{1}\d{3})\s([0]?\d|1\d|2[0-3]):([0-5]\d):([0-5]\d)$/

Matches: 1/19/2011 23:59:59

Captures:

  1. 1
  2. 19
  3. 2011
  4. 23
  5. 59
  6. 59
Community
  • 1
  • 1
Edward J. Stembler
  • 1,932
  • 4
  • 30
  • 53
1
if subject =~ /\A(?:0?[1-9]|1[012])\/(?:0?[1-9]|[12]\d|3[01])\/(?:\d{4})(?:\s+(?:(?:[01]?\d|2[0-3]):(?:[0-5]\d)|(?:0?\d|1[0-2]):(?:[0-5]\d)\s+[ap]m))?\s*\Z/i
    # Successful match

Good luck..

How it works :

    "
^                       # Assert position at the beginning of the string
(?:                     # Match the regular expression below
                           # Match either the regular expression below (attempting the next alternative only if this one fails)
      0                       # Match the character “0” literally
         ?                       # Between zero and one times, as many times as possible, giving back as needed (greedy)
      [1-9]                   # Match a single character in the range between “1” and “9”
   |                       # Or match regular expression number 2 below (the entire group fails if this one fails to match)
      1                       # Match the character “1” literally
      [012]                   # Match a single character present in the list “012”
)
/                       # Match the character “/” literally
(?:                     # Match the regular expression below
                           # Match either the regular expression below (attempting the next alternative only if this one fails)
      0                       # Match the character “0” literally
         ?                       # Between zero and one times, as many times as possible, giving back as needed (greedy)
      [1-9]                   # Match a single character in the range between “1” and “9”
   |                       # Or match regular expression number 2 below (attempting the next alternative only if this one fails)
      [12]                    # Match a single character present in the list “12”
      \d                      # Match a single digit 0..9
   |                       # Or match regular expression number 3 below (the entire group fails if this one fails to match)
      3                       # Match the character “3” literally
      [01]                    # Match a single character present in the list “01”
)
/                       # Match the character “/” literally
(?:                     # Match the regular expression below
   \d                      # Match a single digit 0..9
      {4}                     # Exactly 4 times
)
(?:                     # Match the regular expression below
   \s                      # Match a single character that is a “whitespace character” (spaces, tabs, line breaks, etc.)
      +                       # Between one and unlimited times, as many times as possible, giving back as needed (greedy)
   (?:                     # Match the regular expression below
                              # Match either the regular expression below (attempting the next alternative only if this one fails)
         (?:                     # Match the regular expression below
                                    # Match either the regular expression below (attempting the next alternative only if this one fails)
               [01]                    # Match a single character present in the list “01”
                  ?                       # Between zero and one times, as many times as possible, giving back as needed (greedy)
               \d                      # Match a single digit 0..9
            |                       # Or match regular expression number 2 below (the entire group fails if this one fails to match)
               2                       # Match the character “2” literally
               [0-3]                   # Match a single character in the range between “0” and “3”
         )
         :                       # Match the character “:” literally
         (?:                     # Match the regular expression below
            [0-5]                   # Match a single character in the range between “0” and “5”
            \d                      # Match a single digit 0..9
         )
      |                       # Or match regular expression number 2 below (the entire group fails if this one fails to match)
         (?:                     # Match the regular expression below
                                    # Match either the regular expression below (attempting the next alternative only if this one fails)
               0                       # Match the character “0” literally
                  ?                       # Between zero and one times, as many times as possible, giving back as needed (greedy)
               \d                      # Match a single digit 0..9
            |                       # Or match regular expression number 2 below (the entire group fails if this one fails to match)
               1                       # Match the character “1” literally
               [0-2]                   # Match a single character in the range between “0” and “2”
         )
         :                       # Match the character “:” literally
         (?:                     # Match the regular expression below
            [0-5]                   # Match a single character in the range between “0” and “5”
            \d                      # Match a single digit 0..9
         )
         \s                      # Match a single character that is a “whitespace character” (spaces, tabs, line breaks, etc.)
            +                       # Between one and unlimited times, as many times as possible, giving back as needed (greedy)
         [ap]                    # Match a single character present in the list “ap”
         m                       # Match the character “m” literally
   )
)?                      # Between zero and one times, as many times as possible, giving back as needed (greedy)
\s                      # Match a single character that is a “whitespace character” (spaces, tabs, line breaks, etc.)
   *                       # Between zero and unlimited times, as many times as possible, giving back as needed (greedy)
$                       # Assert position at the end of the string (or before the line break at the end of the string, if any)
"

Remember is not the spoon that bents but you!

FailedDev
  • 26,680
  • 9
  • 53
  • 73
0

Here's what I came up with that seems to work:
regex = /^1?\d{1}\/[123]?\d{1}\/\d{4}(\s[12]?\d:[0-5]\d(:[0-5]\d)?(\s[ap]m)?)?$/

Ryanmt
  • 3,215
  • 3
  • 22
  • 23