b_c
b_c

Reputation: 1212

Catch invalid month/day in "time data does not match format"

I'm converting a date string using the following (not the full code, just the relevant bit):

str_format = '%Y-%m-%d %H:%M:%S'
datetime.strptime('2015-13-23 13:43:23', str_format)

This throws a "time data does not match format" because the month is wrong (13 is not a valid month).

I was wondering if it was possible to have it raise an exception on the month (or day) being invalid instead of the format not matching because of the invalid date? The following clearly shows that datetime can determine that:

>>> print datetime(2015, 13, 23)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: month must be in 1..12

Upvotes: 4

Views: 4314

Answers (2)

Wayne Werner
Wayne Werner

Reputation: 51847

You could always parse the text and then use it to construct a datetime yourself - or pre-validate it if that suits you:

from datetime import datetime

def to_datetime(date_string):
    year = int(date_string[0:4])
    month = int(date_string[5:7])
    day = int(date_string[8:10])

    return datetime(year, month, day)

print(to_datetime('2015-13-23 13:42:13'))

Throws:

Traceback (most recent call last):
  File "test.py", line 11, in <module>
    print(to_datetime('2015-13-23 13:42:13'))
  File "test.py", line 9, in to_datetime
    return datetime(year, month, day)
ValueError: month must be in 1..12

If you want to know how datetime does it - Use the Source, Luke!

>>> import datetime
>>> datetime.__file__
'/usr/lib/python3.5/datetime.py'

Open that file and you'll find:

def _check_date_fields(year, month, day):
    year = _check_int_field(year)
    month = _check_int_field(month)
    day = _check_int_field(day)
    if not MINYEAR <= year <= MAXYEAR:
        raise ValueError('year must be in %d..%d' % (MINYEAR, MAXYEAR), year)
    if not 1 <= month <= 12:
        raise ValueError('month must be in 1..12', month)
    dim = _days_in_month(year, month)
    if not 1 <= day <= dim:
        raise ValueError('day must be in 1..%d' % dim, day)
    return year, month, day

Upvotes: 1

NotAnAmbiTurner
NotAnAmbiTurner

Reputation: 2743

I feel like there are two ways to approach this:

Way 1

str_format = '%Y-%m-%d %H:%M:%S'

try:
    datetime.strptime('2015-13-23 13:43:23', str_format)
except ValueError:
    raise ValueError("Something went wrong")

The downside to this is that it's not constrained to only situations where the month or day are wrong, as your question initially asked.

Way 2

import calendar

str_format = '%Y-%m-%d %H:%M:%S'
datetime_str = '2015-13-23 13:43:23'
year = int(datetime_str[0:4])
month = int(datetime_str[5:7])
day = int(datetime_str[8:10])
cal = calendar.Calendar(6)

if not 1 <= month <= 12:
    raise ValueError("wrong month")
else:
    days_list = list(cal.itermonthdays(year, month))
    if day not in days_list:
        raise ValueError("invalid day")

If you want to use the generator without converting to a list

....

if not 1 <= month <= 12:
    raise ValueError("wrong month")

days_gen = cal.itermonthdays(year, month)

for gen_day in days_gen:
    if gen_day == day:
        break
else:
    raise ValueError("wrong day")

Upvotes: 0

Related Questions