I need to parse RFC 3339 strings like \"2008-09-03T20:56:35.450686Z\"
into Python\'s datetime
type.
I have found strptime in the Python sta
Several answers here suggest using datetime.datetime.strptime to parse RFC 3339 or ISO 8601 datetimes with timezones, like the one exhibited in the question:
2008-09-03T20:56:35.450686Z
This is a bad idea.
Assuming that you want to support the full RFC 3339 format, including support for UTC offsets other than zero, then the code these answers suggest does not work. Indeed, it cannot work, because parsing RFC 3339 syntax using strptime
is impossible. The format strings used by Python's datetime module are incapable of describing RFC 3339 syntax.
The problem is UTC offsets. The RFC 3339 Internet Date/Time Format requires that every date-time includes a UTC offset, and that those offsets can either be Z
(short for "Zulu time") or in +HH:MM
or -HH:MM
format, like +05:00
or -10:30
.
Consequently, these are all valid RFC 3339 datetimes:
2008-09-03T20:56:35.450686Z
2008-09-03T20:56:35.450686+05:00
2008-09-03T20:56:35.450686-10:30
Alas, the format strings used by strptime
and strftime
have no directive that corresponds to UTC offsets in RFC 3339 format. A complete list of the directives they support can be found at https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior, and the only UTC offset directive included in the list is %z
:
%z
UTC offset in the form +HHMM or -HHMM (empty string if the the object is naive).
Example: (empty), +0000, -0400, +1030
This doesn't match the format of an RFC 3339 offset, and indeed if we try to use %z
in the format string and parse an RFC 3339 date, we'll fail:
>>> from datetime import datetime
>>> datetime.strptime("2008-09-03T20:56:35.450686Z", "%Y-%m-%dT%H:%M:%S.%f%z")
Traceback (most recent call last):
File "", line 1, in
File "/usr/lib/python3.4/_strptime.py", line 500, in _strptime_datetime
tt, fraction = _strptime(data_string, format)
File "/usr/lib/python3.4/_strptime.py", line 337, in _strptime
(data_string, format))
ValueError: time data '2008-09-03T20:56:35.450686Z' does not match format '%Y-%m-%dT%H:%M:%S.%f%z'
>>> datetime.strptime("2008-09-03T20:56:35.450686+05:00", "%Y-%m-%dT%H:%M:%S.%f%z")
Traceback (most recent call last):
File "", line 1, in
File "/usr/lib/python3.4/_strptime.py", line 500, in _strptime_datetime
tt, fraction = _strptime(data_string, format)
File "/usr/lib/python3.4/_strptime.py", line 337, in _strptime
(data_string, format))
ValueError: time data '2008-09-03T20:56:35.450686+05:00' does not match format '%Y-%m-%dT%H:%M:%S.%f%z'
(Actually, the above is just what you'll see in Python 3. In Python 2 we'll fail for an even simpler reason, which is that strptime does not implement the %z directive at all in Python 2.)
The multiple answers here that recommend strptime
all work around this by including a literal Z
in their format string, which matches the Z
from the question asker's example datetime string (and discards it, producing a datetime
object without a timezone):
>>> datetime.strptime("2008-09-03T20:56:35.450686Z", "%Y-%m-%dT%H:%M:%S.%fZ")
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)
Since this discards timezone information that was included in the original datetime string, it's questionable whether we should regard even this result as correct. But more importantly, because this approach involves hard-coding a particular UTC offset into the format string, it will choke the moment it tries to parse any RFC 3339 datetime with a different UTC offset:
>>> datetime.strptime("2008-09-03T20:56:35.450686+05:00", "%Y-%m-%dT%H:%M:%S.%fZ")
Traceback (most recent call last):
File "", line 1, in
File "/usr/lib/python3.4/_strptime.py", line 500, in _strptime_datetime
tt, fraction = _strptime(data_string, format)
File "/usr/lib/python3.4/_strptime.py", line 337, in _strptime
(data_string, format))
ValueError: time data '2008-09-03T20:56:35.450686+05:00' does not match format '%Y-%m-%dT%H:%M:%S.%fZ'
Unless you're certain that you only need to support RFC 3339 datetimes in Zulu time, and not ones with other timezone offsets, don't use strptime
. Use one of the many other approaches described in answers here instead.