0

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)

3 Answers 3

4

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.

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

Comments

2
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)

Comments

1

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.

1 Comment

Minor nitpick: I’d personally not want to have a function (used for validation) be able to exit the script.

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.