Sunday, September 2, 2007

Python: Reconstructing datetimes from strings

Previously, I posted a small snippet for converting the str() representation of a python timedelta object back to its equivalent object. This time I'm going to address doing the same for datetime objects:

def parseDateTime(s):
"""Create datetime object representing date/time
expressed in a string

Takes a string in the format produced by calling str()
on a python datetime object and returns a datetime
instance that would produce that string.

Acceptable formats are: "YYYY-MM-DD HH:MM:SS.ssssss+HH:MM",
"YYYY-MM-DD HH:MM:SS.ssssss",
"YYYY-MM-DD HH:MM:SS+HH:MM",
"YYYY-MM-DD HH:MM:SS"
Where ssssss represents fractional seconds. The timezone
is optional and may be either positive or negative
hours/minutes east of UTC.
"""
if s is None:
return None
# Split string in the form 2007-06-18 19:39:25.3300-07:00
# into its constituent date/time, microseconds, and
# timezone fields where microseconds and timezone are
# optional.
m = re.match(r'(.*?)(?:\.(\d+))?(([-+]\d{1,2}):(\d{2}))?$',
str(s))
datestr, fractional, tzname, tzhour, tzmin = m.groups()

# Create tzinfo object representing the timezone
# expressed in the input string. The names we give
# for the timezones are lame: they are just the offset
# from UTC (as it appeared in the input string). We
# handle UTC specially since it is a very common case
# and we know its name.
if tzname is None:
tz = None
else:
tzhour, tzmin = int(tzhour), int(tzmin)
if tzhour == tzmin == 0:
tzname = 'UTC'
tz = FixedOffset(timedelta(hours=tzhour,
minutes=tzmin), tzname)

# Convert the date/time field into a python datetime
# object.
x = datetime.strptime(datestr, "%Y-%m-%d %H:%M:%S")

# Convert the fractional second portion into a count
# of microseconds.
if fractional is None:
fractional = '0'
fracpower = 6 - len(fractional)
fractional = float(fractional) * (10 ** fracpower)

# Return updated datetime object with microseconds and
# timezone information.
return x.replace(microsecond=int(fractional), tzinfo=tz)

Last time Lawrence Oluyede was kind enough to point out that the dateutil module can likely do this and a lot more. However, I'm trying to stick to modules in the base library. Of course, it wouldn't be a bad thing if dateutil were to make it into the base library....

Speaking of which, the snipped above relies on the FixedOffset tzinfo object described in the documentation for the datetime.tzinfo module. Being that the documentation is part of the standard python distribution, I guess you could call that code part of the base library, even if you can't import it. :|

Update 2007/09/03 1:39pm (JST):
Fix example format examples in doc-string per comment from David Goodger.

3 comments:

David Goodger said...

Your docstring contains errors: YYYY:MM:DD should be YYYY-MM-DD.

Kelly Yancey said...

David: Thanks, I just updated the docstring in the posting to correct the error.

GamesBook said...

Thanks for this code - really useful! Just one small problem. At the end you say - "snipped above relies on the FixedOffset tzinfo object". This object (from: ) has the following constructor:

def __init__(self, offset, name):
self.__offset = timedelta(minutes = offset)

and your code passes in an offset which is already a timedelta object, thereby causing a type mismatch error. Please can you update your code accordingly.