How to convert a timezone aware string to datetime in Python without dateutil?
Asked Answered
V

7

135

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")
Vibraphone answered 1/11, 2012 at 17:7 Comment(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?Goetz
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.Vibraphone
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.Goetz
N
155

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.

Noble answered 1/11, 2012 at 17:13 Comment(7)
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.Vaginismus
@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.Noble
it is not that painful to define a FixedOffset class. Here's code exampleAuditory
Python 3.9, fromisoformat fails at Z in string or a decimal in time`Sn
@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.Noble
@MartijnPieters, I meant this 2021-09-23T04:34:59.408ZSn
@Jashwant: datetime.fromisoformat(dt.replace("Z", "+00:00")) parses that string (if dt is the variable with that string value).Noble
H
74

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
Hosey answered 12/4, 2016 at 7:18 Comment(3)
This is supposed to be the Correct answer for doing this without external libSalamis
@Salamis python-dateutil is exactly "external lib".Katonah
The top answer didn't work for me with the trailing 'Z', but this answer did.Cough
S
27

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
Sugihara answered 14/6, 2017 at 1:48 Comment(3)
When it's wrong, why does print(t) add the colon to the utc offset?Kostival
@Kostival 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.Sugihara
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.Ljubljana
C
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')
Courtly answered 25/3, 2019 at 11:4 Comment(0)
R
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)
Reimpression answered 24/6, 2021 at 14:57 Comment(2)
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.Carolanncarole
Thanks! super useful. I can try out this other comment about datetime.timezone.utc... but really I just needed something that worked.Hartsell
A
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.

Ario answered 1/6, 2017 at 14:39 Comment(0)
B
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
Brokerage answered 27/3, 2021 at 20:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.