237

I have a python method which accepts a date input as a string.

How do I add a validation to make sure the date string being passed to the method is in the ffg. format:

'YYYY-MM-DD'

if it's not, method should raise some sort of error

3
  • 3
    It might be more Pythonic (ask for forgiveness, not permission) not to check at all, and catch any resulting exceptions that occur. Commented Jun 1, 2013 at 8:23
  • 1
    Related: In python, how to check if a date is valid? Commented Jul 27, 2015 at 20:35
  • Nice loop @kenorb Commented May 23, 2024 at 8:37

5 Answers 5

354
>>> import datetime
>>> def validate(date_text):
        try:
            datetime.date.fromisoformat(date_text)
        except ValueError:
            raise ValueError("Incorrect data format, should be YYYY-MM-DD")

    
>>> validate('2003-12-23')
>>> validate('2003-12-32')

Traceback (most recent call last):
  File "<pyshell#20>", line 1, in <module>
    validate('2003-12-32')
  File "<pyshell#18>", line 5, in validate
    raise ValueError("Incorrect data format, should be YYYY-MM-DD")
ValueError: Incorrect data format, should be YYYY-MM-DD

Note that datetime.date.fromisoformat() obviously works only when date is in ISO format. If you need to check date in some other format, use datetime.datetime.strptime().

Sign up to request clarification or add additional context in comments.

8 Comments

Is there a way of doing that without a try/except? Python tends to slow down significantly when an exception is raised and caught.
@chiffa You could match a date format regex but its not recommended because it's less robust and exceptions are clearer. Are you sure date validation is your bottleneck?
Not really, so in the end I will just wrap throw-except construct in a function. I am just surprised that there is no bool-returning validating function that would trigger the Exception throw in the datetime library.
For those who want zero padding in dates, this solution won't work as strptime is not strict about zero padding. Implement a regex of your own or check the length of the resultant string after stripping whitespace and then make use of this solution .
@MiaoLiu In your use case you've found the method that is fastest in raw speed, you've done the right thing. this is a more generic solution for a different problem
|
107

The Python dateutil library is designed for this (and more). It will automatically convert this to a datetime object for you and raise a ValueError if it can't.

As an example:

>>> from dateutil.parser import parse
>>> parse("2003-09-25")
datetime.datetime(2003, 9, 25, 0, 0)

This raises a ValueError if the date is not formatted correctly:

>>> parse("2003-09-251")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/jacinda/envs/dod-backend-dev/lib/python2.7/site-packages/dateutil/parser.py", line 720, in parse
    return DEFAULTPARSER.parse(timestr, **kwargs)
  File "/Users/jacinda/envs/dod-backend-dev/lib/python2.7/site-packages/dateutil/parser.py", line 317, in parse
    ret = default.replace(**repl)
ValueError: day is out of range for month

dateutil is also extremely useful if you start needing to parse other formats in the future, as it can handle most known formats intelligently and allows you to modify your specification: dateutil parsing examples.

It also handles timezones if you need that.

Update based on comments: parse also accepts the keyword argument dayfirst which controls whether the day or month is expected to come first if a date is ambiguous. This defaults to False. E.g.

>>> parse('11/12/2001')
>>> datetime.datetime(2001, 11, 12, 0, 0) # Nov 12
>>> parse('11/12/2001', dayfirst=True)
>>> datetime.datetime(2001, 12, 11, 0, 0) # Dec 11

7 Comments

it may accept too much e.g., parse('13/12/2001') is "13 Dec" but parse('11/12/2001') is "12 Nov" (the first result would suggest "11 Dec" here).
parse actually takes a dayfirst keyword argument that allows you to control this. parse('11/12/2001', dayfirst=True) will return "11 Dec." dateutil's default is dayfirst=False
you are missing the point that datetutil.parser.parse() accepts too many time formats (you could find other examples with ambiguous input). If you want to validate that your input is in YYYY-MM-DD format then the parse() function is the wrong tool.
That's a completely valid point - if you really want to restrict to just that specific format this doesn't do that, and the accepted answer already does a great job of doing the right thing in that case. I think when I wrote the answer I was thinking more along the lines of pointing out how to validate whether it was a valid date as opposed to the particular format that the author requested, which when people come across this question is what they're often looking for.
Is there a way to get .parse() to return the format string in addition to the datetime object?
|
80

I think the full validate function should look like this:

from datetime import datetime

def validate(date_text):
    try:
        if date_text != datetime.strptime(date_text, "%Y-%m-%d").strftime('%Y-%m-%d'):
            raise ValueError
        return True
    except ValueError:
        return False

Executing just

datetime.strptime(date_text, "%Y-%m-%d") 

is not enough because strptime method doesn't check that month and day of the month are zero-padded decimal numbers. For example

datetime.strptime("2016-5-3", '%Y-%m-%d')

will be executed without errors.

4 Comments

"You are technically correct - the best kind of correct." I needed to ensure this in my strings.
This works fine against my tests, however I the documentation seems incorrect as it states: "%d -> Day of the month as a zero-padded decimal number -> 01, 02, …, 31" and the same for the %m -> Month as a zero-padded decimal number. -> 01, 02, …, 12 docs.python.org/2/library/…
If you need to check that month and day are zero-padded, wouldn't it suffice just to check the length of the string and datetime.strptime(date_text, "%Y-%m-%d")?
this is the only correct answer. especially when there are multiple datetime formats that need to be supported
27
from datetime import datetime

datetime.strptime(date_string, "%Y-%m-%d")

..this raises a ValueError if it receives an incompatible format.

..if you're dealing with dates and times a lot (in the sense of datetime objects, as opposed to unix timestamp floats), it's a good idea to look into the pytz module, and for storage/db, store everything in UTC.

3 Comments

You were faster, I would have posted it myself (ideone.com/vuxDDf). Upvote.
..just saw it right after it was posted, and happen to have been working with datetime objects today.
Eleven years later and this is still the best answer :) Thanks!
8

From mere curiosity, I timed the two rivalling answers posted above.
And I had the following results:

dateutil.parser (valid str): 4.6732222699938575
dateutil.parser (invalid str): 1.7270505399937974
datetime.strptime (valid): 0.7822393209935399
datetime.strptime (invalid): 0.4394566189876059

And here's the code I used (Python 3.6)


from dateutil import parser as date_parser
from datetime import datetime
from timeit import timeit


def is_date_parsing(date_str):
    try:
        return bool(date_parser.parse(date_str))
    except ValueError:
        return False


def is_date_matching(date_str):
    try:
        return bool(datetime.strptime(date_str, '%Y-%m-%d'))
    except ValueError:
        return False



if __name__ == '__main__':
    print("dateutil.parser (valid date):", end=' ')
    print(timeit("is_date_parsing('2021-01-26')",
                 setup="from __main__ import is_date_parsing",
                 number=100000))

    print("dateutil.parser (invalid date):", end=' ')
    print(timeit("is_date_parsing('meh')",
                 setup="from __main__ import is_date_parsing",
                 number=100000))

    print("datetime.strptime (valid date):", end=' ')
    print(timeit("is_date_matching('2021-01-26')",
                 setup="from __main__ import is_date_matching",
                 number=100000))

    print("datetime.strptime (invalid date):", end=' ')
    print(timeit("is_date_matching('meh')",
                 setup="from __main__ import is_date_matching",
                 number=100000))

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.