128

So in Python 3, you can generate an ISO 8601 date with .isoformat(), but you can't convert a string created by isoformat() back into a datetime object because Python's own datetime directives don't match properly. That is, %z = 0500 instead of 05:00 (which is produced by .isoformat()).

For example:

>>> strDate = d.isoformat()
>>> strDate
'2015-02-04T20:55:08.914461+00:00'

>>> objDate = datetime.strptime(strDate,"%Y-%m-%dT%H:%M:%S.%f%z")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python34\Lib\_strptime.py", line 500, in _strptime_datetime
    tt, fraction = _strptime(data_string, format)
  File "C:\Python34\Lib\_strptime.py", line 337, in _strptime
    (data_string, format))
ValueError: time data '2015-02-04T20:55:08.914461+00:00' does not match format '%Y-%m-%dT%H:%M:%S.%f%z'

From Python's strptime documentation: (https://docs.python.org/2/library/datetime.html#strftime-strptime-behavior)

%z UTC offset in the form +HHMM or -HHMM (empty string if the the object is naive). (empty), +0000, -0400, +1030

So, in short, Python does not even adhere to its own string formatting directives.

I know datetime is already terrible in Python, but this really goes beyond unreasonable into the land of plain stupidity.

Tell me this isn't true.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Alex Urcioli
  • 2,883
  • 3
  • 11
  • 17
  • 2
    Have you considered stripping the last colon from the isoformatted string and then parsing back to a datetime object? It's a workaround, that can still be done somewhat elegantly. – Oliver W. Feb 04 '15 at 22:04
  • @OliverW. I did consider this, in the end I ended up giving in and installing python-dateutil. I don't actually consider this a good solution either. I think those who are responsible for python need to take a closer look at what they've done with datetime. As of now i've given up and will just jump through the hoops like everyone else does. – Alex Urcioli Feb 05 '15 at 00:16
  • I have always actually been particularly impressed with python for even HAVING functions like strptime and strftime, I never even took the time to notice this particular deficiency because as already stated, it can be rather elegantly avoided. – Darren Ringer Feb 05 '15 at 00:24
  • 2
    I agree, this is ridiculous - the native platform can convert to an ISO date but it cannot convert it back, except if you use another third party module. – notzippy Feb 09 '17 at 18:21
  • 1
    Starting from Python 3.7 a new functionality has been introduced to the `%z`: ```Changed in version 3.7: When the %z directive is provided to the strptime() method, the UTC offsets can have a colon as a separator between hours, minutes and seconds. For example, '+01:00:00' will be parsed as an offset of one hour. In addition, providing 'Z' is identical to '+00:00'.``` – garlix Feb 05 '20 at 13:30

2 Answers2

131

Python 3.7+

As of Python 3.7 there is a method datetime.fromisoformat() which is exactly the reverse for isoformat().

Older Python

If you have older Python, then this is the current best "solution" to this question:

pip install python-dateutil

Then...

import datetime
import dateutil

def getDateTimeFromISO8601String(s):
    d = dateutil.parser.parse(s)
    return d
erik
  • 2,278
  • 1
  • 23
  • 30
Alex Urcioli
  • 2,883
  • 3
  • 11
  • 17
  • 132
    Sad state of Python's datetime... – Daniele Venzano Sep 11 '15 at 09:13
  • 2
    Worth noting that if your system's time is set to UTC, the timezone of the parsed date will be `tzlocal()`, which `!= tzutc` in comparisons, according to this link: https://coderwall.com/p/dpauza/dateutil-parse-timezone – Adam Barnes Aug 30 '16 at 16:40
  • @DanieleVenzano I don't get it. How can this string return back in a more happy way? – raratiru Oct 14 '17 at 21:28
  • 7
    @raratiru I was referring to the fact that you need an external module to parse a timestamp in ISO format, but you can generate one with the standard library. Inconsistencies make me sad. – Daniele Venzano Oct 16 '17 at 06:14
  • @DanieleVenzano Oh, indeed I understand. This is a point, thank you! – raratiru Oct 16 '17 at 13:26
  • 18
    In python 3.7 there is a method [`datetime.fromisoformat()`](https://docs.python.org/3.8/library/datetime.html#datetime.date.fromisoformat) which is exactly the reverse for `isoformat()` – Konrad Jan 13 '20 at 10:06
25

Try this:

>>> def gt(dt_str):
...     dt, _, us = dt_str.partition(".")
...     dt = datetime.datetime.strptime(dt, "%Y-%m-%dT%H:%M:%S")
...     us = int(us.rstrip("Z"), 10)
...     return dt + datetime.timedelta(microseconds=us)

Usage:

>>> gt("2008-08-12T12:20:30.656234Z")
datetime.datetime(2008, 8, 12, 12, 20, 30, 656234)
cmaher
  • 5,100
  • 1
  • 22
  • 34
user 12321
  • 2,846
  • 1
  • 24
  • 34
  • 6
    Your solution doesn't address the problem that was laid out (and is pretty much a repetition of the possible duplicate that was already linked to). In the original problem, the OP has asked for an efficient way to parse the UTC offset, which has a colon in it. – Oliver W. Feb 04 '15 at 22:00
  • This solution is good whenever installing one extra library is somewhat inconvenient (e.g. on Google Appengine) – Luca Nov 21 '17 at 21:56