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.

<span>>>></span> <span>timestamp_str</span> <span>=</span> <span>"2023-07-19T18:15:10-07:00"</span>
<span>>>></span> <span>timestamp_str</span> <span>=</span> <span>"2023-07-19T18:15:10-07:00"</span>
>>> 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!

<span>>>></span> <span>from</span> <span>datetime</span> <span>import</span> <span>datetime</span>
<span>>>></span> <span>timestamp</span> <span>=</span> <span>datetime</span><span>.</span><span>fromisoformat</span><span>(</span><span>timestamp_str</span><span>)</span>
<span>>>></span> <span>from</span> <span>datetime</span> <span>import</span> <span>datetime</span>
<span>>>></span> <span>timestamp</span> <span>=</span> <span>datetime</span><span>.</span><span>fromisoformat</span><span>(</span><span>timestamp_str</span><span>)</span>
>>> from datetime import datetime >>> timestamp = datetime.fromisoformat(timestamp_str)

Enter fullscreen mode Exit fullscreen mode

Is it in the past?

<span>>>></span> <span>timestamp</span> <span><</span> <span>datetime</span><span>.</span><span>now</span><span>()</span>
<span>Traceback</span> <span>(</span><span>most</span> <span>recent</span> <span>call</span> <span>last</span><span>):</span>
<span>File</span> <span>"<stdin>"</span><span>,</span> <span>line</span> <span>1</span><span>,</span> <span>in</span> <span><</span><span>module</span><span>></span>
<span>TypeError</span><span>:</span> <span>can</span><span>'t compare offset-naive and offset-aware datetimes </span>
<span>>>></span> <span>timestamp</span> <span><</span> <span>datetime</span><span>.</span><span>now</span><span>()</span>
<span>Traceback</span> <span>(</span><span>most</span> <span>recent</span> <span>call</span> <span>last</span><span>):</span>
  <span>File</span> <span>"<stdin>"</span><span>,</span> <span>line</span> <span>1</span><span>,</span> <span>in</span> <span><</span><span>module</span><span>></span>
<span>TypeError</span><span>:</span> <span>can</span><span>'t compare offset-naive and offset-aware datetimes </span>
>>> 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.

<span>>>></span> <span>timestamp</span><span>.</span><span>isoformat</span><span>()</span>
<span>'2023-07-19T18:15:10-07:00'</span>
<span>>>></span> <span>datetime</span><span>.</span><span>now</span><span>().</span><span>isoformat</span><span>()</span>
<span>'2023-07-19T18:24:10.978546'</span>
<span>>>></span> <span>timestamp</span><span>.</span><span>isoformat</span><span>()</span>
<span>'2023-07-19T18:15:10-07:00'</span>
<span>>>></span> <span>datetime</span><span>.</span><span>now</span><span>().</span><span>isoformat</span><span>()</span>
<span>'2023-07-19T18:24:10.978546'</span>
>>> 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.

<span>>>></span> <span>from</span> <span>datetime</span> <span>import</span> <span>timezone</span>
<span>>>></span> <span>datetime</span><span>.</span><span>now</span><span>(</span><span>timezone</span><span>.</span><span>utc</span><span>).</span><span>isoformat</span><span>()</span>
<span>'2023-07-19T18:25:38.4567+00:00'</span>
<span>>>></span> <span>timestamp</span> <span><</span> <span>datetime</span><span>.</span><span>now</span><span>(</span><span>timezone</span><span>.</span><span>utc</span><span>)</span>
<span>False</span>
<span>>>></span> <span>from</span> <span>datetime</span> <span>import</span> <span>timezone</span>
<span>>>></span> <span>datetime</span><span>.</span><span>now</span><span>(</span><span>timezone</span><span>.</span><span>utc</span><span>).</span><span>isoformat</span><span>()</span>
<span>'2023-07-19T18:25:38.4567+00:00'</span>
<span>>>></span> <span>timestamp</span> <span><</span> <span>datetime</span><span>.</span><span>now</span><span>(</span><span>timezone</span><span>.</span><span>utc</span><span>)</span>
<span>False</span>
>>> 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.

<span>>>></span> <span>from</span> <span>datetime</span> <span>import</span> <span>timedelta</span>
<span>>>></span> <span>timezone</span><span>(</span><span>timedelta</span><span>(</span><span>hours</span><span>=</span><span>2</span><span>))</span>
<span>datetime</span><span>.</span><span>timezone</span><span>(</span><span>datetime</span><span>.</span><span>timedelta</span><span>(</span><span>seconds</span><span>=</span><span>7200</span><span>))</span>
<span>>>></span> <span>datetime</span><span>.</span><span>now</span><span>(</span><span>timezone</span><span>(</span><span>timedelta</span><span>(</span><span>hours</span><span>=</span><span>2</span><span>))).</span><span>isoformat</span><span>()</span>
<span>'2023-07-19T18:31:355.529775+02:00'</span>
<span>>>></span> <span>timestamp</span> <span><</span> <span>datetime</span><span>.</span><span>now</span><span>(</span><span>timezone</span><span>(</span><span>timedelta</span><span>(</span><span>hours</span><span>=</span><span>2</span><span>)))</span>
<span>False</span>
<span>>>></span> <span>from</span> <span>datetime</span> <span>import</span> <span>timedelta</span>
<span>>>></span> <span>timezone</span><span>(</span><span>timedelta</span><span>(</span><span>hours</span><span>=</span><span>2</span><span>))</span>
<span>datetime</span><span>.</span><span>timezone</span><span>(</span><span>datetime</span><span>.</span><span>timedelta</span><span>(</span><span>seconds</span><span>=</span><span>7200</span><span>))</span>
<span>>>></span> <span>datetime</span><span>.</span><span>now</span><span>(</span><span>timezone</span><span>(</span><span>timedelta</span><span>(</span><span>hours</span><span>=</span><span>2</span><span>))).</span><span>isoformat</span><span>()</span>
<span>'2023-07-19T18:31:355.529775+02:00'</span>
<span>>>></span> <span>timestamp</span> <span><</span> <span>datetime</span><span>.</span><span>now</span><span>(</span><span>timezone</span><span>(</span><span>timedelta</span><span>(</span><span>hours</span><span>=</span><span>2</span><span>)))</span>
<span>False</span>
>>> 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.

<span>>>></span> <span>datetime</span><span>.</span><span>now</span><span>().</span><span>isoformat</span><span>()</span>
<span>'2023-07-19T18:37:47.447466'</span>
<span>>>></span> <span>datetime</span><span>.</span><span>now</span><span>(</span><span>timezone</span><span>.</span><span>utc</span><span>).</span><span>isoformat</span><span>()</span>
<span>'2023-07-19T16:37:55.417677+00:00'</span>
<span>>>></span> <span>datetime</span><span>.</span><span>now</span><span>(</span><span>timezone</span><span>.</span><span>utc</span><span>).</span><span>astimezone</span><span>(</span><span>timezone</span><span>(</span><span>timedelta</span><span>(</span><span>hours</span><span>=</span><span>2</span><span>))).</span><span>isoformat</span><span>()</span>
<span>'2023-07-19T18:38:32.881836+02:00'</span>
<span>>>></span> <span>datetime</span><span>.</span><span>now</span><span>(</span><span>timezone</span><span>.</span><span>utc</span><span>).</span><span>astimezone</span><span>().</span><span>isoformat</span><span>()</span>
<span>'2023-07-19T18:39:25.116842+02:00'</span>
<span>>>></span> <span>datetime</span><span>.</span><span>now</span><span>().</span><span>isoformat</span><span>()</span>
<span>'2023-07-19T18:37:47.447466'</span>
<span>>>></span> <span>datetime</span><span>.</span><span>now</span><span>(</span><span>timezone</span><span>.</span><span>utc</span><span>).</span><span>isoformat</span><span>()</span>
<span>'2023-07-19T16:37:55.417677+00:00'</span>
<span>>>></span> <span>datetime</span><span>.</span><span>now</span><span>(</span><span>timezone</span><span>.</span><span>utc</span><span>).</span><span>astimezone</span><span>(</span><span>timezone</span><span>(</span><span>timedelta</span><span>(</span><span>hours</span><span>=</span><span>2</span><span>))).</span><span>isoformat</span><span>()</span>
<span>'2023-07-19T18:38:32.881836+02:00'</span>
<span>>>></span> <span>datetime</span><span>.</span><span>now</span><span>(</span><span>timezone</span><span>.</span><span>utc</span><span>).</span><span>astimezone</span><span>().</span><span>isoformat</span><span>()</span>
<span>'2023-07-19T18:39:25.116842+02:00'</span>
>>> 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.

<span>>>></span> <span>datetime</span><span>.</span><span>now</span><span>(</span><span>timezone</span><span>.</span><span>utc</span><span>).</span><span>astimezone</span><span>().</span><span>tzinfo</span>
<span>datetime</span><span>.</span><span>timezone</span><span>(</span><span>datetime</span><span>.</span><span>timedelta</span><span>(</span><span>seconds</span><span>=</span><span>7200</span><span>),</span> <span>'CEST'</span><span>)</span>
<span>>>></span> <span>local_timezone</span> <span>=</span> <span>datetime</span><span>.</span><span>now</span><span>(</span><span>timezone</span><span>.</span><span>utc</span><span>).</span><span>astimezone</span><span>().</span><span>tzinfo</span>
<span>>>></span> <span>datetime</span><span>.</span><span>now</span><span>().</span><span>isoformat</span><span>()</span>
<span>'2023-07-19T18:40:56.4701'</span>
<span>>>></span> <span>datetime</span><span>.</span><span>now</span><span>(</span><span>local_timezone</span><span>).</span><span>isoformat</span><span>()</span>
<span>'2023-07-19T18:41:02.30460+02:00'</span>
<span>>>></span> <span>datetime</span><span>.</span><span>now</span><span>(</span><span>timezone</span><span>.</span><span>utc</span><span>).</span><span>astimezone</span><span>().</span><span>tzinfo</span>
<span>datetime</span><span>.</span><span>timezone</span><span>(</span><span>datetime</span><span>.</span><span>timedelta</span><span>(</span><span>seconds</span><span>=</span><span>7200</span><span>),</span> <span>'CEST'</span><span>)</span>
<span>>>></span> <span>local_timezone</span> <span>=</span> <span>datetime</span><span>.</span><span>now</span><span>(</span><span>timezone</span><span>.</span><span>utc</span><span>).</span><span>astimezone</span><span>().</span><span>tzinfo</span>
<span>>>></span> <span>datetime</span><span>.</span><span>now</span><span>().</span><span>isoformat</span><span>()</span>
<span>'2023-07-19T18:40:56.4701'</span>
<span>>>></span> <span>datetime</span><span>.</span><span>now</span><span>(</span><span>local_timezone</span><span>).</span><span>isoformat</span><span>()</span>
<span>'2023-07-19T18:41:02.30460+02:00'</span>
>>> 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.

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

<span>>>></span> <span>datetime</span><span>.</span><span>fromisoformat</span><span>(</span><span>timestamp_str</span><span>)</span>
<span>datetime</span><span>.</span><span>datetime</span><span>(</span><span>2023</span><span>,</span> <span>7</span><span>,</span> <span>19</span><span>,</span> <span>18</span><span>,</span> <span>39</span><span>,</span> <span>25</span><span>)</span>
<span>>>></span> <span>pdt_timezone</span> <span>=</span> <span>timezone</span><span>(</span><span>timedelta</span><span>(</span><span>hours</span><span>=-</span><span>7</span><span>))</span>
<span>>>></span> <span>datetime</span><span>.</span><span>fromisoformat</span><span>(</span><span>timestamp_str</span><span>).</span><span>astimezone</span><span>(</span><span>pdt_timezone</span><span>)</span>
<span>datetime</span><span>.</span><span>datetime</span><span>(</span><span>2023</span><span>,</span> <span>7</span><span>,</span> <span>19</span><span>,</span> <span>9</span><span>,</span> <span>39</span><span>,</span> <span>25</span><span>,</span> <span>tzinfo</span><span>=</span><span>datetime</span><span>.</span><span>timezone</span><span>(</span><span>datetime</span><span>.</span><span>timedelta</span><span>(</span><span>days</span><span>=-</span><span>1</span><span>,</span> <span>seconds</span><span>=</span><span>61200</span><span>)))</span>
<span>>>></span> <span>datetime</span><span>.</span><span>fromisoformat</span><span>(</span><span>timestamp_str</span><span>)</span>
<span>datetime</span><span>.</span><span>datetime</span><span>(</span><span>2023</span><span>,</span> <span>7</span><span>,</span> <span>19</span><span>,</span> <span>18</span><span>,</span> <span>39</span><span>,</span> <span>25</span><span>)</span>
<span>>>></span> <span>pdt_timezone</span> <span>=</span> <span>timezone</span><span>(</span><span>timedelta</span><span>(</span><span>hours</span><span>=-</span><span>7</span><span>))</span>
<span>>>></span> <span>datetime</span><span>.</span><span>fromisoformat</span><span>(</span><span>timestamp_str</span><span>).</span><span>astimezone</span><span>(</span><span>pdt_timezone</span><span>)</span>
<span>datetime</span><span>.</span><span>datetime</span><span>(</span><span>2023</span><span>,</span> <span>7</span><span>,</span> <span>19</span><span>,</span> <span>9</span><span>,</span> <span>39</span><span>,</span> <span>25</span><span>,</span> <span>tzinfo</span><span>=</span><span>datetime</span><span>.</span><span>timezone</span><span>(</span><span>datetime</span><span>.</span><span>timedelta</span><span>(</span><span>days</span><span>=-</span><span>1</span><span>,</span> <span>seconds</span><span>=</span><span>61200</span><span>)))</span>
>>> 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.

<span>>>></span> <span>datetime</span><span>.</span><span>fromisoformat</span><span>(</span><span>timestamp_str</span><span>).</span><span>replace</span><span>(</span><span>tzinfo</span><span>=</span><span>pdt_timezone</span><span>)</span>
<span>datetime</span><span>.</span><span>datetime</span><span>(</span><span>2023</span><span>,</span> <span>7</span><span>,</span> <span>19</span><span>,</span> <span>18</span><span>,</span> <span>39</span><span>,</span> <span>25</span><span>,</span> <span>tzinfo</span><span>=</span><span>datetime</span><span>.</span><span>timezone</span><span>(</span><span>datetime</span><span>.</span><span>timedelta</span><span>(</span><span>days</span><span>=-</span><span>1</span><span>,</span> <span>seconds</span><span>=</span><span>61200</span><span>)))</span>
<span>>>></span> <span>datetime</span><span>.</span><span>fromisoformat</span><span>(</span><span>timestamp_str</span><span>).</span><span>replace</span><span>(</span><span>tzinfo</span><span>=</span><span>pdt_timezone</span><span>)</span>
<span>datetime</span><span>.</span><span>datetime</span><span>(</span><span>2023</span><span>,</span> <span>7</span><span>,</span> <span>19</span><span>,</span> <span>18</span><span>,</span> <span>39</span><span>,</span> <span>25</span><span>,</span> <span>tzinfo</span><span>=</span><span>datetime</span><span>.</span><span>timezone</span><span>(</span><span>datetime</span><span>.</span><span>timedelta</span><span>(</span><span>days</span><span>=-</span><span>1</span><span>,</span> <span>seconds</span><span>=</span><span>61200</span><span>)))</span>
>>> 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.

原文链接:Timezones in Python datetime objects

© 版权声明
THE END
喜欢就支持一下吧
点赞14 分享
Don't look back, you're not going that way.
别回头,你要走的不是那条路
评论 抢沙发

请登录后发表评论

    暂无评论内容