Search code examples
rubydatetimestring-formattingdatetime-format

Validate if a string matches a format string


In Ruby, you can parse a string into a DateTime based on a given format string, for example:

3.2.4 :001 > DateTime.strptime(Time.now.to_s, '%Y-%m-%d %H:%M')
Sat, 17 Aug 2024 13:31:00 +0000
3.2.4 :002 > Time.now.to_s
"2024-08-17 13:31:52 -0400"

Notice how strptime works even if the current time is more detailed than '%Y-%m-%d %H:%M', which doesn't have seconds or timezone in it.

What I'd like to have is a way to validate a string like '2024-08-15 08:00' actually conforms to a format.

I know that you can use a regex to validate something like this, but I'd prefer something that could check for a datetime format string without necessarily even parsing.

My preference here is because %Y-%m-%d %H:%M looks cleaner than /\A\d{4}-\d{2}-\d{2} \d{2}:\d{2}\z/ (also, if it happens to validate that it's a real datetime rather than 9999-99-99 99:99 that's a bonus, though it would be helpful either way).


Solution

  • Searching through Ruby's strptime implementation, I found DateTime::_strptime (it is publicly documented but starts with _ for some reason) which returns a Hash that contains a :leftover string containing the trailing part of the string that was skipped.

    If the string is consumed completely, the key does not exist in the returned hash. If the parsing fails, the function returns nil.

    require "date"
    
    def strptime?(format, input)
      if (hash = DateTime._strptime(format, input))
        !hash.key?(:leftover)
      else
        false
      end
    end
    
    p strptime?(Time.now.to_s, "%Y-%m-%d %H:%M")
    p strptime?(Time.now.to_s[..14], "%Y-%m-%d %H:%M")
    p strptime?(Time.now.to_s[..15], "%Y-%m-%d %H:%M")
    p strptime?(Time.now.to_s[..16], "%Y-%m-%d %H:%M")
    p strptime?("", "%Y-%m-%d %H:%M")
    

    Output:

    false
    true
    true
    false
    false