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.
暂无评论内容