4

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
b_c
  • 1,202
  • 13
  • 24
  • The most common way to get the error is to swap the month and day, since US and European customs are opposite, so the error as is makes sense. – Mark Ransom Jan 08 '16 at 17:01
  • Why are you passing in the 13th month? – NotAnAmbiTurner Jan 08 '16 at 17:19
  • Not sure if it's the proper way, but may be you can raise an exception from and exception block – Iron Fist Jan 08 '16 at 17:21
  • 2
    @NotAnAmbiTurner I think that's the point - perhaps someone fat-fingered the month, or as Mark pointed out they're used to a different format and put something like `2015-13-08` – Wayne Werner Jan 08 '16 at 17:28
  • @NotAnAmbiTurner, Wayne's right, I'm trying to handle a situation where someone inputs an invalid month/day (such as the 13th month), and to alert them to that instead of the "does not match format". The string _does_ match the format, it's just that the month is wrong, which isn't clear by the exception it ends up giving. – b_c Jan 08 '16 at 20:23

2 Answers2

1

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
Wayne Werner
  • 49,299
  • 29
  • 200
  • 290
  • should the day slice be `date_string[8:10]`? – NotAnAmbiTurner Jan 08 '16 at 17:50
  • 1
    @NotAnAmbiTurner ah, you're right. Apparently `int` disregards trailing spaces, though :) – Wayne Werner Jan 08 '16 at 17:57
  • @WayneWerner: It's (a little) more work than I was expecting, but this certainly covers it! I tried to look up the datetime.py file, but [apparently](http://stackoverflow.com/a/269825/4739755) I'll have to go fetch a tarball to get to it on Windows :-/ – b_c Jan 08 '16 at 20:53
  • 1
    Oh interesting. You can also go find the source on [GitHub](https://github.com/python/cpython/find/master) – Wayne Werner Jan 08 '16 at 20:58
0

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")
NotAnAmbiTurner
  • 2,553
  • 2
  • 21
  • 44