Martin Vegter
Martin Vegter

Reputation: 527

Python: match correct datetime.datetime format

In my Python script, I pass a commandline option --before with a date argument, i.e.

myscript.py --before 2014-Aug-02

I then need to read it into my variable. I want to support several possible date formats, i.e. 2014-Aug-02, 2014-08-02, 2014-08, ...

In my script, I have following construct to try to match the provided date format, but it looks to me very ugly. Is there a better, more elegant way to do it?

if args.before:
    try:
        BEFORE = datetime.datetime.strptime(args.before[0], "%Y-%b-%d")
    except ValueError:
        try:
            BEFORE = datetime.datetime.strptime(args.before[0], "%Y-%b")
        except ValueError:
            try:
                 BEFORE = datetime.datetime.strptime(args.before[0], "%Y-%m-%d")
            except ValueError:
                try:
                    BEFORE = datetime.datetime.strptime(args.before[0], "%Y-%m")
                except ValueError:
                    try:
                        BEFORE = datetime.datetime.strptime(args.before[0], "%Y")
                    except ValueError:
                        print 'time data %s does not match format' % args.before[0]
                        exit(1)

Upvotes: 0

Views: 352

Answers (3)

jonrsharpe
jonrsharpe

Reputation: 121976

I would do something like:

def validate_date(datestr):
    for format in ("%Y-%b-%d", "%Y-%b", ...):
        try:
            return datetime.datetime.strptime(datestr, format)
        except ValueError:
            pass
    else:
        print 'time data %s does not match format' % datestr
        exit(1)

Then:

BEFORE = validate_date(args.before[0])

The else block on a for loop runs if the loop reaches an end without break or return, which in this case only happens if no format ever successfully parses the datestr.


If, like @poke, you prefer to have the function not exit the whole script (as may not always be appropriate), you can remove exit(1) from the function entirely (which will therefore implicitly return None if no format matches) and do something like:

BEFORE = validate_date(args.before[0])
if BEFORE is None:
    exit(1)

You could alternatively move the whole else block outside the function, as you might not always want it to print anything, either.

Upvotes: 1

Martijn Pieters
Martijn Pieters

Reputation: 1121524

I'd personally use the dateutil package to parse arbitrary dates:

from dateutil.parser import parse as date_parse
try:
    BEFORE = date_parse(args.before[0])
except (TypeError, ValueError):
    print 'time data %s does not match format' % args.before[0]
    exit(1)

The dateutil parser can handle all formats you want to support plus more.

But if you don't want to install an external dependency, then create a list of supported formats, and use a loop:

formats = ('%Y-%b-%d', '%Y-%b', '%Y-%m-%d', '%Y-%m', '%Y')
for format in formats:
    try:
        BEFORE = datetime.datetime.strptime(args.before[0], format)
        break
    except ValueError:
        pass
else:
    print 'time data %s does not match format' % args.before[0]
    exit(1)

As soon as a working format is found, the loop is ended with a break, otherwise the else: suite of the for loop is reached, and an error message is printed. Note that an else: suite of a loop is not executed when you break out early.

Upvotes: 4

poke
poke

Reputation: 387557

acceptedFormats = ("%Y-%b-%d", "%Y-%b", "%Y-%m-%d", "%Y-%m", "%Y")
if args.before:
    for format in acceptedFormats:
        try:
            before = datetime.datetime.strptime(args.before[0], format)
            break
        except ValueError:
            pass
    else:
        print 'time data %s does not match format' % args.before[0]
        exit(1)

Upvotes: 2

Related Questions