129

I have to convert a timezone-aware string like "2012-11-01T04:16:13-04:00" to a Python datetime object.

I saw the dateutil module which has a parse function, but I don't really want to use it as it adds a dependency.

So how can I do it? I have tried something like the following, but with no luck.

datetime.datetime.strptime("2012-11-01T04:16:13-04:00", "%Y-%m-%dT%H:%M:%S%Z")
Martin Thoma
  • 124,992
  • 159
  • 614
  • 958
lxyu
  • 2,661
  • 5
  • 23
  • 29
  • 3
    What's wrong with adding a dependency when that dependency precisely fills your requirements? Surely if the same results could be achieved without the extra module, there'd be no reason for the module to exist at all, would there? Just how hard is it for you to add a dependency? – Jon Skeet Nov 01 '12 at 17:09
  • I think it's maybe a personal favor? I don't really want to introduce a whole big module into the project since I only need a tiny single function. – lxyu Nov 01 '12 at 17:15
  • 3
    What's the *concrete cost* of adding a dependency to your project, compared with the cost of making your code harder to understand than it needs to be. Ignore the fact that you only *currently* need a single function - concentrate on the costs. – Jon Skeet Nov 01 '12 at 17:31

7 Answers7

152

As of Python 3.7, datetime.datetime.fromisoformat() can handle your format:

>>> import datetime
>>> datetime.datetime.fromisoformat('2012-11-01T04:16:13-04:00')
datetime.datetime(2012, 11, 1, 4, 16, 13, tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=72000)))

In older Python versions you can't, not without a whole lot of painstaking manual timezone defining.

Python does not include a timezone database, because it would be outdated too quickly. Instead, Python relies on external libraries, which can have a far faster release cycle, to provide properly configured timezones for you.

As a side-effect, this means that timezone parsing also needs to be an external library. If dateutil is too heavy-weight for you, use iso8601 instead, it'll parse your specific format just fine:

>>> import iso8601
>>> iso8601.parse_date('2012-11-01T04:16:13-04:00')
datetime.datetime(2012, 11, 1, 4, 16, 13, tzinfo=<FixedOffset '-04:00'>)

iso8601 is a whopping 4KB small. Compare that tot python-dateutil's 148KB.

As of Python 3.2 Python can handle simple offset-based timezones, and %z will parse -hhmm and +hhmm timezone offsets in a timestamp. That means that for a ISO 8601 timestamp you'd have to remove the : in the timezone:

>>> from datetime import datetime
>>> iso_ts = '2012-11-01T04:16:13-04:00'
>>> datetime.strptime(''.join(iso_ts.rsplit(':', 1)), '%Y-%m-%dT%H:%M:%S%z')
datetime.datetime(2012, 11, 1, 4, 16, 13, tzinfo=datetime.timezone(datetime.timedelta(-1, 72000)))

The lack of proper ISO 8601 parsing is being tracked in Python issue 15873.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • It seems to me `datetime` could stand to include something like `iso8601` to handle ISO 8601 timezones -- a bit of parsing and two `tzinfo` subclasses. – Eryk Sun Nov 01 '12 at 17:32
  • @eryksun: ISO8601 is really simplistic about timezones, but once you include those offsets in the python stdlib, you'll be flooded by misunderstandings of why real-life timezones (which are more than a mere offset) don't work, etc. – Martijn Pieters Nov 01 '12 at 17:35
  • 1
    it is not that painful to define a `FixedOffset` class. Here's [code example](http://stackoverflow.com/a/23122493/4279) – jfs Feb 03 '15 at 17:14
  • Python 3.9, `fromisoformat` fails at Z in string or a decimal in time` – Jashwant Dec 23 '21 at 07:56
  • 1
    @Jashwant: it'll fail in any Python version. Use `isoformattedstring.replace("Z", "+00:00")` if you must accept strings using `Z`. Not sure what you mean by *decimal in time*. – Martijn Pieters Jan 19 '22 at 21:37
  • @MartijnPieters, I meant this `2021-09-23T04:34:59.408Z` – Jashwant Jan 20 '22 at 07:08
  • @Jashwant: `datetime.fromisoformat(dt.replace("Z", "+00:00"))` parses that string (if `dt` is the variable with that string value). – Martijn Pieters Jan 20 '22 at 08:05
73

Here is the Python Doc for datetime object using dateutil package..

from dateutil.parser import parse

get_date_obj = parse("2012-11-01T04:16:13-04:00")
print get_date_obj
Mohideen bin Mohammed
  • 18,813
  • 10
  • 112
  • 118
26

There are two issues with the code in the original question: there should not be a : in the timezone and the format string for "timezone as an offset" is lower case %z not upper %Z.

This works for me in Python v3.6

>>> from datetime import datetime
>>> t = datetime.strptime("2012-11-01T04:16:13-0400", "%Y-%m-%dT%H:%M:%S%z")
>>> print(t)
2012-11-01 04:16:13-04:00
simhumileco
  • 31,877
  • 16
  • 137
  • 115
Jamie Czuy
  • 1,343
  • 1
  • 11
  • 16
  • When it's wrong, why does `print(t)` add the colon to the utc offset? – moooeeeep Aug 09 '17 at 09:58
  • @moooeeeep Because by default datetime uses the `isoformat(sep=' ')` for the `__str__` function which prints the UTC offset as "+HH:MM". Using `print(t.strftime("%Y-%m-%dT%H:%M:%S%z"))` will print without the ":" in the timezone. – Jamie Czuy Aug 21 '17 at 20:05
  • 4
    Having a colon in the timezone isn't wrong. Many sources present their times in string form: `2012-11-01T04:16:13-04:00`. OP is seeking to parse that form. – DaveL17 Feb 28 '18 at 13:52
7

You can convert like this.

date = datetime.datetime.strptime('2019-3-16T5-49-52-595Z','%Y-%m-%dT%H-%M-%S-%f%z')
date_time = date.strftime('%Y-%m-%dT%H:%M:%S.%fZ')
nick
  • 1,090
  • 1
  • 11
  • 24
5

You can create a timezone unaware object and replace the tzinfo and make it a timezone aware DateTime object later.

from datetime import datetime
import pytz

unaware_time = datetime.strptime("2012-11-01 04:16:13", "%Y-%m-%d %H:%M:%S")
aware_time = unaware_time.replace(tzinfo=pytz.UTC)
moken
  • 3,227
  • 8
  • 13
  • 23
Mowli sri
  • 71
  • 1
  • 8
  • This is the easiest way, but what has always annoyed me is that you create a `datetime` object twice, since `replace` does *not* simply replace the tzinfo, it creates a completely new object. Also, since Python 3.2 you can use `datetime.timezone.utc`, no need for pytz. – Krachtwerk Nov 19 '21 at 13:48
  • Thanks! super useful. I can try out this other comment about datetime.timezone.utc... but really I just needed something that worked. – Patricia Green Apr 14 '22 at 17:41
1

I'm new to Python, but found a way to convert

2017-05-27T07:20:18.000-04:00 to

2017-05-27T07:20:18 without downloading new utilities.

from datetime import datetime, timedelta

time_zone1 = int("2017-05-27T07:20:18.000-04:00"[-6:][:3])
>>returns -04

item_date = datetime.strptime("2017-05-27T07:20:18.000-04:00".replace(".000", "")[:-6], "%Y-%m-%dT%H:%M:%S") + timedelta(hours=-time_zone1)

I'm sure there are better ways to do this without slicing up the string so much, but this got the job done.

wiseco68
  • 41
  • 3
1

This suggestion for using dateutil by Mohideen bin Mohammed definitely is the best solution even if it does a require a small library. having used the other approaches there prone to various forms of failure. Here's a nice function for this.

from dateutil.parser import parse


def parse_date_convert(date, fmt=None):
    if fmt is None:
        fmt = '%Y-%m-%d %H:%M:%S' # Defaults to : 2022-08-31 07:47:30
    get_date_obj = parse(str(date))
    return str(get_date_obj.strftime(fmt))

dates = ['2022-08-31T07:47:30Z','2022-08-31T07:47:29.098Z','2017-05-27T07:20:18.000-04:00','2012-11-01T04:16:13-04:00']

for date in dates:
    print(f'Before: {date}  After: {parse_date_convert(date)}')

Results:

Before: 2022-08-31T07:47:30Z  After: 2022-08-31 07:47:30
Before: 2022-08-31T07:47:29.098Z  After: 2022-08-31 07:47:29
Before: 2017-05-27T07:20:18.000-04:00  After: 2017-05-27 07:20:18
Before: 2012-11-01T04:16:13-04:00  After: 2012-11-01 04:16:13

Having tried various forms such as slicing split replacing the T Z like this:

dates = ['2022-08-31T07:47:30Z','2022-08-31T07:47:29.098Z','2017-05-27T07:20:18.000-04:00','2012-11-01T04:16:13-04:00']

for date in dates:
    print(f'Before: {date}  After: {date.replace("T", " ").replace("Z", "")}')

You still are left with subpar results. like the below

Before: 2022-08-31T07:47:30Z  After: 2022-08-31 07:47:30
Before: 2022-08-31T07:47:29.098Z  After: 2022-08-31 07:47:29.098
Before: 2017-05-27T07:20:18.000-04:00  After: 2017-05-27 07:20:18.000-04:00
Before: 2012-11-01T04:16:13-04:00  After: 2012-11-01 04:16:13-04:00
Mike R
  • 679
  • 7
  • 13