34

Is there an equivalent to C#'s DateTime.TryParse() in Python?

I'm referring to the fact that it avoids throwing an exception, not the fact that it guesses the format.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
user541686
  • 205,094
  • 128
  • 528
  • 886
  • 1
    General note: You won't find many `try_parse_*` methods in Python. The idiom usually is "Easier to ask for forgiveness than permission", i.e. just do it and catch exceptions (if you can handle them). –  Jul 07 '11 at 18:56
  • 1
    @delnan: C#'s `DateTime.TryParse` guesses at the time format of a string. It's not a case of "look before you leap." – Steven Rumbalski Jul 07 '11 at 19:44
  • @Steven: `if (!DateTime.TryParse(input, date)) { /* error message */ }` *is* LBYL. The EAFP version if `try { date = DateTime.Parse(input); } except (SomeException) { /* error message */ }`. Unless you're trying to tell me `TryParse` methods generally do `try { ...; return true; } except (...) { return false; } `. –  Jul 07 '11 at 19:46
  • 1
    @delnan: At issue in this question is to find a method in Python that guesses at the date format of a string. EAFP vs LBYL is orthagonal to this question. The word "try" in the method name is not referring to EAFP or LBYL. – Steven Rumbalski Jul 07 '11 at 19:55
  • 1
    @Steven: I was actually referring to the fact that it doesn't throw an exception, not the fact that it guesses the date format... I'll clarify that – user541686 Jul 07 '11 at 22:11
  • Well, looks like I was totally wrong. Answer deleted. – Steven Rumbalski Jul 08 '11 at 00:26
  • This question title is unintelligible to 99.9% of Python programmers, I strongly want to edit it to: **"How to try...catch multiple datetime formats and suppress/handle the exceptions for all those that mismatch?"**. Strip it of the C# jargon. – smci Dec 18 '17 at 19:46
  • Related question: [Converting string into datetime](https://stackoverflow.com/questions/466345/converting-string-into-datetime). Probably that should be the canonical question. – smci Dec 18 '17 at 21:12
  • PeterMortensen: in response to your latest edit, you can make any Python function throw an exception simply by checking if its output is correct, then raising whichever Exception if not. – smci Aug 24 '18 at 21:11

8 Answers8

28

If you don't want the exception, catch the exception.

try:
    d = datetime.datetime.strptime(s, "%Y-%m-%d %H:%M:%S")
except ValueError:
    d = None

In the zen of Python, explicit is better than implicit. strptime always returns a datetime parsed in the exact format specified. This makes sense, because you have to define the behavior in case of failure, maybe what you really want is.

except ValueError:
    d = datetime.datetime.now()

or

except ValueError:
    d = datetime.datetime.fromtimestamp(0)

or

except ValueError:
    raise WebFramework.ServerError(404, "Invalid date")

By making it explicit, it's clear to the next person who reads it what the failover behavior is, and that it is what you need it to be.


Or maybe you're confident that the date cannot be invalid; it's coming from a database DATETIME, column, in which case there won't be an exception to catch, and so don't catch it.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
SingleNegationElimination
  • 151,563
  • 33
  • 264
  • 304
  • 9
    "If you don't want the exception, catch the exception." did you really just tell me to use exceptions for flow control? x____x – user541686 Jul 07 '11 at 22:27
  • 3
    I told you to use exceptions for *exception handling*, and what is a malformed date if not an exceptional case? – SingleNegationElimination Jul 07 '11 at 22:28
  • 1
    It's either a date or something else (an integer, etc.). It's perfectly valid in my program, I just need to know which it is. – user541686 Jul 07 '11 at 22:35
  • ... how come you don't already know the type? Short of NLP, I can't think of too many ways that "One of N possible Types, but not *sure* which" would be valid in *my* program. In any case, `except ValueError: pass`, but I weep. – SingleNegationElimination Jul 07 '11 at 22:41
  • 1
    I just *don't*, because that's how my program is working... it's getting a little off-topic, sorry. I weep too. :( – user541686 Jul 07 '11 at 22:44
  • 1
    @anonymous downvoter: I'm sorry you disagree with my answer; This is really boils down to "No, there is no python equivalent to c#'s `DateTime.TryParse()` because TryParse *is not pythonic*". All parsers in python raise `ValueError` when they encounter input they cannot parse. – SingleNegationElimination Jul 11 '11 at 18:53
  • 1
    `TryParse` with an `if/else` is from the behaviour standpoint no different from a `try/catch` but it's much cleaner than handling an exception that isn't one. There is no such thing as _idiomatic pythonic_ or maybe not implementing member accessibility in the language itself and using the `_` underscore as a workaround is? LOL – t3chb0t Oct 18 '21 at 12:12
14

We want to try...catch multiple datetime formats fmt1,fmt2,...,fmtn and suppress/handle the exceptions (from strptime) for all those that mismatch (and in particular, avoid needing a yukky n-deep indented ladder of try..catch clauses). I found two elegant ways, the second is best in general. (This is big problem on real-world data where multiple, mismatched, incomplete, inconsistent and multilanguage/region date formats are often mixed freely in one dataset.)

1) Individually try applying each format and handle each individual strptime() fail as a return-value of None, so you can chain fn calls...

To start off, adapting from @OrWeis' answer for compactness:

def try_strptime_single_format(s, fmt):
    try:
        return datetime.datetime.strptime(s, fmt)
    except ValueError:
        return None

Now you can invoke as try_strptime(s, fmt1) or try_strptime(s, fmt2) or try_strptime(s, fmt3) ... But we can improve that to:

2) Apply multiple possible formats (either pass in as an argument or use sensible defaults), iterate over those, catch and handle any errors internally:

Cleaner, simpler and more OO-friendly is to generalize this to make the formats parameter either a single string or a list, then iterate over that... so your invocation reduces to try_strptime(s, [fmt1, fmt2, fmt3, ...])

def try_strptime(s, fmts=['%d-%b-%y','%m/%d/%Y']):
    for fmt in fmts:
        try:
            return datetime.strptime(s, fmt)
        except:
            continue

    return None # or reraise the ValueError if no format matched, if you prefer

(As a sidebar, note that ...finally is not the droid we want, since it would execute after each loop pass i.e. on each candidate format, not once at the end of the loop.)

I find implementation 2) is cleaner and better. In particular the function/method can store a list of default formats, which makes it more failsafe and less exception-happy on real-world data. (We could even infer which default formats to apply based on other columns, e.g. first try German date formats on German data, Arabic on Arabic, weblog datetime formats on weblog data etc.)

smci
  • 32,567
  • 20
  • 113
  • 146
5

Here's an equivalent function implementation

import datetime

def try_strptime(s, format):
    """
    @param s the string to parse
    @param format the format to attempt parsing of the given string
    @return the parsed datetime or None on failure to parse 
    @see datetime.datetime.strptime
    """
    try:
        date = datetime.datetime.strptime(s, format)
    except ValueError:
        date = None
    return date
Or Weis
  • 524
  • 5
  • 5
  • Beautiful! This is actually the nicest way to implement, if we need to try multiple formats and don't want to build a clunky n-deep indented `try..except` ladder. You probably want to point out the elegance of this: you can chain it `try_strptime(s, fmt1) or try_strptime(s, fmt2) or try_strptime(s, fmt3) ...`. You could even generalize this to make `formats` parameter either a single string or a list, and iterate over them... `try_strptime(s, [fmt1, fmt2, fmt3, ...])` – smci Dec 15 '17 at 01:41
  • I generalized your answer to [mine below](https://stackoverflow.com/questions/6615533/is-there-a-python-equivalent-to-cs-datetime-tryparse/47876446#47876446), I think it's even nicer... – smci Dec 18 '17 at 21:09
5

No, what you're asking for is not idiomatic Python, and so there generally won't be functions that discard errors like that in the standard library. The relevant standard library modules are documented here:

http://docs.python.org/library/datetime.html

http://docs.python.org/library/time.html

The parsing functions all raise exceptions on invalid input.

However, as the other answers have stated, it wouldn't be terribly difficult to construct one for your application (your question was phrased "in Python" rather than "in the Python standard library" so it's not clear if assistance writing such a function "in Python" is answering the question or not).

Eli Stevens
  • 1,447
  • 1
  • 12
  • 21
  • What's the best way to enumerate the exceptions that might be thrown? – qneill Feb 27 '17 at 23:31
  • I doubt there is one. `except Exception:` if you want to catch 'em all (almost, see also `BaseException`). – Eli Stevens Feb 28 '17 at 23:39
  • 2
    @qneill You mean catch several exceptions in one line to treat them in the same way? `except ValueError, KeyboardError:` (py3); `except (ValueError, KeyboardError):` (py2) – Natacha Nov 28 '20 at 18:06
3

Brute force is also an option:

def TryParse(datestring, offset):
    nu = datetime.datetime.now()
    retval = nu
    formats = ["%d-%m-%Y","%Y-%m-%d","%d-%m-%y","%y-%m-%d"]  

    if datestring == None:
        retval = datetime.datetime(nu.year,nu.month,nu.day,0,0,0) - datetime.timedelta(offset,0,0,0,0,0,0)
    elif datestring == '':
        retval = datetime.datetime(nu.year,nu.month,nu.day,0,0,0) - datetime.timedelta(offset,0,0,0,0,0,0) 
    else:
        succes = False
        for aformat in formats:
            try:
                retval = datetime.datetime.strptime(datestring,aformat)
                succes = True
                break
            except:
                pass
        if not succes:
            retval = datetime.datetime(nu.year,nu.month,nu.day,0,0,0) - datetime.timedelta(offset,0,0,0,0,0,0) 
    return retval
marbel82
  • 925
  • 1
  • 18
  • 39
Jeroen Bom
  • 41
  • 1
  • You can easily refactor the subtracting `datetime.timedelta(offset,0,0,0,0,0,0)` to the bottom of the ladder. In fact this is more amenable to a helper function, `return retval` takes one line. – smci Apr 26 '18 at 22:12
0

I agree tryparse is very useful function on c#. Unfortunately no equivalent direct function of that in python (may be i am not aware). I believe you want to check a string is whether date or not without worrying about date format. My recommendation is go for pandas to_datetime function:

def is_valid_date(str_to_validate: str) -> bool:
    try:
        if pd.to_datetime(str_to_validate):
            return True
        else:
            return False
    except ValueError:
        return False
Aravinth
  • 18
  • 5
0

Use time.strptime to parse dates from strings.

Documentation: http://docs.python.org/library/time.html#time.strptime

Examples from: http://pleac.sourceforge.net/pleac_python/datesandtimes.html

#----------------------------- 
# Parsing Dates and Times from Strings

time.strptime("Tue Jun 16 20:18:03 1981")
# (1981, 6, 16, 20, 18, 3, 1, 167, -1)

time.strptime("16/6/1981", "%d/%m/%Y")
# (1981, 6, 16, 0, 0, 0, 1, 167, -1)
# strptime() can use any of the formatting codes from time.strftime()

# The easiest way to convert this to a datetime seems to be; 
now = datetime.datetime(*time.strptime("16/6/1981", "%d/%m/%Y")[0:5])
# the '*' operator unpacks the tuple, producing the argument list.
FogleBird
  • 74,300
  • 25
  • 125
  • 131
-2

How about strptime?

http://docs.python.org/library/time.html#time.strptime

It will throw a ValueError if it is unable to parse the string based on the format that is provided.


Edit:

Since the question was edited to include the bit about exceptions after I answered it. I wanted to add a note about that.

As was pointed out in other answers, if you don't want your program to raise an exception, you can simply catch it and handle it:

try:
    date = datetime.datetime.strptime(s, "%Y-%m-%d %H:%M:%S")
except ValueError:
    date = None

That's a Pythonic way to do what you want.

jncraton
  • 9,022
  • 3
  • 34
  • 49
  • 1
    I'd hate to give a -1 (sorry...), but there's a reason I said the equivalent of **`TryParse`** and not `Parse`... – user541686 Jul 07 '11 at 22:08