13

I would like to parse a date that can come in several formats, that I know beforehand. If I could not parse, I return nil. In ruby, I do like this:

DATE_FORMATS = ['%m/%d/%Y %I:%M:%S %p', '%Y/%m/%d %H:%M:%S', '%d/%m/%Y %H:%M', '%m/%d/%Y', '%Y/%m/%d']

def parse_or_nil(date_str)
    parsed_date = nil
    DATE_FORMATS.each do |f|
        parsed_date ||= DateTime.strptime(date_str, f) rescue nil
    end
    parsed_date
end

This is concise and works. How can I do the same thing in Python?

Luís Guilherme
  • 2,620
  • 6
  • 26
  • 41

4 Answers4

19

I would just try dateutil. It can recognize most of the formats:

from dateutil import parser
parser.parse(string)

if you end up using datetime.strptime as suggested @RocketDonkey:

from datetime import datetime

def func(s,flist):
    for f in flist:
        try:
            return datetime.strptime(s,f)
        except ValueError:
            pass
root
  • 76,608
  • 25
  • 108
  • 120
13

You can use try/except to catch the ValueError that would occur when trying to use a non-matching format. As @Bakuriu mentions, you can stop the iteration when you find a match to avoid the unnecessary parsing, and then define your behavior when my_date doesn't get defined because not matching formats are found:

You can use try/except to catch the ValueError that would occur when trying to use a non-matching format:

from datetime import datetime

DATE_FORMATS = ['%m/%d/%Y %I:%M:%S %p', '%Y/%m/%d %H:%M:%S', '%d/%m/%Y %H:%M', '%m/%d/%Y', '%Y/%m/%d']
test_date = '2012/1/1 12:32:11'

for date_format in DATE_FORMATS:
    try:
        my_date = datetime.strptime(test_date, date_format)
    except ValueError:
        pass
    else:
      break
else:
  my_date = None

print my_date # 2012-01-01 12:32:11
print type(my_date) # <type 'datetime.datetime'>
RocketDonkey
  • 36,383
  • 7
  • 80
  • 84
  • I'd use a break even if not necessary. It avoids unnecessary parsing and exception handling. Also, this fails to define `my_date` when no format is correct. Adding a break you can use python's `else` clause to the `for` to define `my_date` as `None`. – Bakuriu Jan 09 '13 at 19:53
  • @Bakuriu Great point, will amend (last time I write something before heading to lunch :) ). – RocketDonkey Jan 09 '13 at 20:03
  • Great answer, not concise enough, but if I cannot find anything better, I will accept this. – Luís Guilherme Jan 09 '13 at 21:11
  • You can do `my_date = datetime.strptime(...); break` instead of using the `else` with the `try` statement. Anyway there isn't anything more concise. `strptime` raises a `ValueError` for invalid formats and you must handle it. An alternative to the `try` statement would be to use a `with` statement and let a context manager handle the exception, but it would require more code(even though later you could re-use the manager in other situations). – Bakuriu Jan 09 '13 at 21:28
3

After your tip, RocketDonkey, and the one from Bakuriu, I could write a shorter version. Any problem with it?

def parse_or_none(date):
    for date_format in DATE_FORMATS:
        try:
            return datetime.strptime(date, date_format)
        except ValueError:
            pass
    return None
Bakuriu
  • 98,325
  • 22
  • 197
  • 231
Luís Guilherme
  • 2,620
  • 6
  • 26
  • 41
  • 1
    Looks good to me - you can take out the `return None` as the function will return `None` by default if it gets to that point without returning. – RocketDonkey Jan 09 '13 at 21:43
  • +1 That seems pretty concise, I was writing it down at the same time, I guess that's the obvious way to do it :) – root Jan 09 '13 at 21:45
  • 2
    I actually like the final `return None` because, it's true that it is not necessary but it states explicitly that returning `None` if no format is found is part of the function interface and not just a negligence/bug. – Bakuriu Jan 12 '13 at 14:34
1

Modifying root's answer to handle m/d/y:

from dateutil import parser
parser.parse(string, dayfirst=False)

There's also a yearfirst option, to prefer y/m/d.

Jiri Baum
  • 51
  • 1