DEV Community

rkucsora
rkucsora

Posted on

Timezones in Python datetime objects

I've learned something useful about Python timezone handling in datetime objects.

Let's suppose I got a timestamp from an external source. It's in a string variable now.

>>> timestamp_str = "2023-07-19T18:15:10-07:00"
Enter fullscreen mode Exit fullscreen mode

Cool, it's in ISO format, let's parse it!

>>> from datetime import datetime
>>> timestamp = datetime.fromisoformat(timestamp_str)
Enter fullscreen mode Exit fullscreen mode

Is it in the past?

>>> timestamp < datetime.now()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't compare offset-naive and offset-aware datetimes
Enter fullscreen mode Exit fullscreen mode

What? One of these datetime objects contain timezone information, the other one doesn't. They don't play with each other nicely.

>>> timestamp.isoformat()
'2023-07-19T18:15:10-07:00'
>>> datetime.now().isoformat()
'2023-07-19T18:24:10.978546'
Enter fullscreen mode Exit fullscreen mode

OK, how can I add timezone to now? I'm in Central European Summer Time (CEST) at the moment. But as I learned, it's a bit complicated. I will get back to this later, let's work with UTC, it's easier. datetime.now accepts a timezone parameter.

>>> from datetime import timezone
>>> datetime.now(timezone.utc).isoformat()
'2023-07-19T18:25:38.4567+00:00'
>>> timestamp < datetime.now(timezone.utc)
False
Enter fullscreen mode Exit fullscreen mode

That's better. But can I use my own timezone here? I can, if I know what my timezone is. CEST is UTC +2. The timezone class is here to help.

>>> from datetime import timedelta
>>> timezone(timedelta(hours=2))
datetime.timezone(datetime.timedelta(seconds=7200))
>>> datetime.now(timezone(timedelta(hours=2))).isoformat()
'2023-07-19T18:31:355.529775+02:00'
>>> timestamp < datetime.now(timezone(timedelta(hours=2)))
False
Enter fullscreen mode Exit fullscreen mode

But what if I don't know my timezone? How can I get the local timezone? If it's a remote host, it can be anywhere. What I found is that the datetime.astimezone() method can help here. If it gets a target timezone parameter, it converts the datetime to that timezone. If it gets nothing (or None) it uses the system local timezone.

>>> datetime.now().isoformat()
'2023-07-19T18:37:47.447466'
>>> datetime.now(timezone.utc).isoformat()
'2023-07-19T16:37:55.417677+00:00'
>>> datetime.now(timezone.utc).astimezone(timezone(timedelta(hours=2))).isoformat()
'2023-07-19T18:38:32.881836+02:00'
>>> datetime.now(timezone.utc).astimezone().isoformat()
'2023-07-19T18:39:25.116842+02:00'
Enter fullscreen mode Exit fullscreen mode

Yep, I'm really in CEST. I can get that info in the tzinfo attribute.

>>> datetime.now(timezone.utc).astimezone().tzinfo
datetime.timezone(datetime.timedelta(seconds=7200), 'CEST')
>>> local_timezone = datetime.now(timezone.utc).astimezone().tzinfo
>>> datetime.now().isoformat()
'2023-07-19T18:40:56.4701'
>>> datetime.now(local_timezone).isoformat()
'2023-07-19T18:41:02.30460+02:00'
Enter fullscreen mode Exit fullscreen mode

So astimezone works fine with no parameters, but I find it hard to read. I'm still looking for the really readable solution.

But here comes the next challenge, if you are still not dizzy. You read the following timestamp from a textfile.

>>> timestamp_str = "2023-07-19T18:39:25"
Enter fullscreen mode Exit fullscreen mode

It doesn't contain timezone information, but you know that it's in Pacific Daylight Time. How can you add that timezone information to the datetime object?

Can datetime.astimezone help here? It not just simply adds the timezone information to the datetime, it adjusts date and time accordingly.

>>> datetime.fromisoformat(timestamp_str)
datetime.datetime(2023, 7, 19, 18, 39, 25)
>>> pdt_timezone = timezone(timedelta(hours=-7))
>>> datetime.fromisoformat(timestamp_str).astimezone(pdt_timezone)
datetime.datetime(2023, 7, 19, 9, 39, 25, tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=61200)))
Enter fullscreen mode Exit fullscreen mode

This is not exactly what I wanted. It turned out that the solution is simply replacing tzinfo in the datetime object.

>>> datetime.fromisoformat(timestamp_str).replace(tzinfo=pdt_timezone)
datetime.datetime(2023, 7, 19, 18, 39, 25, tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=61200)))
Enter fullscreen mode Exit fullscreen mode

Much better. Happy? Kind of. Can we just store every timestamp with timezone info added? Or everything it UTC? Thanks.

Top comments (1)

Collapse
 
olodocoder profile image
Adams Adebayo

Hi rkucsora!

Thanks for sharing your knowledge on how to deal with timezones in Python with us here on Dev!

However, you should consider grouping the paragraphs of your article into sections so readers can easily read and assimilate what you’re trying to teach.

Thanks again and Happy coding!