Timezones in Python datetime objects
Tags: python, todayilearned, programming
Published: 2023-07-26 19:51:04

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.
```python
>>> timestamp_str = "2023-07-19T18:15:10-07:00"
```

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

```python
>>> from datetime import datetime
>>> timestamp = datetime.fromisoformat(timestamp_str)
```

Is it in the past?

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

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

```python
>>> timestamp.isoformat()
'2023-07-19T18:15:10-07:00'
>>> datetime.now().isoformat()
'2023-07-19T18:24:10.978546'
```

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.

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

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.

```python
>>> 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
```

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.

```python
>>> 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'
```

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

```python
>>> 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'
```

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.
```python
>>> timestamp_str = "2023-07-19T18:39:25"
```
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.

```python
>>> 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)))
```

This is not exactly what I wanted. It turned out that the solution is simply replacing `tzinfo` in the datetime object.
```python
>>> 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)))
```

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