145

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")
3
  • 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? Commented Nov 1, 2012 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. Commented Nov 1, 2012 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. Commented Nov 1, 2012 at 17:31

7 Answers 7

166

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.

Sign up to request clarification or add additional context in comments.

7 Comments

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.
it is not that painful to define a FixedOffset class. Here's code example
Python 3.9, fromisoformat fails at Z in string or a decimal in time`
@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.
@Jashwant: datetime.fromisoformat(dt.replace("Z", "+00:00")) parses that string (if dt is the variable with that string value).
|
75

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

3 Comments

This is supposed to be the Correct answer for doing this without external lib
@Paullo python-dateutil is exactly "external lib".
The top answer didn't work for me with the trailing 'Z', but this answer did.
28

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

3 Comments

When it's wrong, why does print(t) add the colon to the utc offset?
@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.
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.
10

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)

2 Comments

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.
Thanks! super useful. I can try out this other comment about datetime.timezone.utc... but really I just needed something that worked.
8

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')

Comments

2

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

Comments

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.