How to calculate timedelta between datetimes with different timezones in Python - python

How do you get a valid timedelta instance when differencing datetimes with different timezones in Python? I'm finding the timedelta is always 0 if the timezones are different.
>>> from dateutil.parser import parse
>>> dt0=parse('2017-02-06 18:14:32-05:00')
>>> dt0
datetime.datetime(2017, 2, 6, 18, 14, 32, tzinfo=tzoffset(None, -18000))
>>> dt1=parse('2017-02-06 23:14:32+00:00')
>>> dt1
datetime.datetime(2017, 2, 6, 23, 02, 12, tzinfo=tzutc())
>>> (dt1-dt0).total_seconds()
0.0
This doesn't make any sense to me. I would have thought that Python's datetime class would be smart enough to normalize both values to UTC internally, and then return a timedelta based on those values. Or throw an exception. Instead it returns 0, implying both datetimes are equal, which clearly they're not. What am I doing wrong here?

You are confused about what the timezone means; the two times you gave are identical, so of course their difference is zero. I can duplicate your results, except that I don't have the discrepancy between the second string and second datetime that you have:
>>> from dateutil.parser import parse
>>> dt0=parse('2017-02-06 18:14:32-05:00')
>>> dt0
datetime.datetime(2017, 2, 6, 18, 14, 32, tzinfo=tzoffset(None, -18000))
>>> dt1=parse('2017-02-06 23:14:32+00:00')
>>> dt1
datetime.datetime(2017, 2, 6, 23, 14, 32, tzinfo=tzutc())
>>> (dt1-dt0).total_seconds()
0.0
But watch what happens when I convert dt0 to UTC. The time gets adjusted by the 5 hour timezone difference, and it becomes identical to the second.
>>> dt0.astimezone(dt1.tzinfo)
datetime.datetime(2017, 2, 6, 23, 14, 32, tzinfo=tzutc())

Related

Unexpected UTC offsets of fractions of an hour using PYTZ with common timezones

I am having trouble understanding the effect of timezone offsets using pytz.
I start with a time in UTC, like this:
>>> utc = datetime.datetime(2021, 3, 5, 7, 59, 58, tzinfo=pytz.timezone("UTC"))
Now I take the same wall clock time in EST, which is 5 standard meridians to the west and should differ (I believe) by 5 hours.
>>> est = datetime.datetime(2021, 3, 5, 7, 59, 58, tzinfo=pytz.timezone("US/Eastern"))
>>> est-utc
datetime.timedelta(seconds=17760)
>>> 17760/3600
4.933333333333334
That difference is not the 5 hours I expect, but 4h56m, short by 4 minutes
Continuing to the west I try to arrive at times with one hour successively earlier:
>>> cst = datetime.datetime(2021, 3, 5, 7, 59, 58, tzinfo=pytz.timezone("US/Central"))
>>> cst-utc
datetime.timedelta(seconds=21060)
>>> 21060/3600
5.85
That difference is not the expected 6 hours, but 5h51, short by 9 minutes.
>>> mst = datetime.datetime(2021, 3, 5, 7, 59, 58, tzinfo=pytz.timezone("US/Mountain"))
>>> mst-utc
datetime.timedelta(seconds=25200)
>>> 25200/3600
7.0
This yields the expected 7-hour difference.
>>> pst = datetime.datetime(2021, 3, 5, 7, 59, 58, tzinfo=pytz.timezone("US/Pacific"))
>>> pst-utc
datetime.timedelta(seconds=28380)
>>> 28380/3600
7.883333333333334
Now the difference is not the expected 8 hours but 7h53, short by 7 minutes.
And if I display the times, this is what I get:
>>> est
datetime.datetime(2021, 3, 5, 7, 59, 58, tzinfo=<DstTzInfo 'US/Eastern' LMT-1 day, 19:04:00 STD>)
>>> cst
datetime.datetime(2021, 3, 5, 7, 59, 58, tzinfo=<DstTzInfo 'US/Central' LMT-1 day, 18:09:00 STD>)
>>> mst
datetime.datetime(2021, 3, 5, 7, 59, 58, tzinfo=<DstTzInfo 'US/Mountain' LMT-1 day, 17:00:00 STD>)
>>> pst
datetime.datetime(2021, 3, 5, 7, 59, 58, tzinfo=<DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>)
So datetime is fully aware that the UTC offsets of these times vary from the expected even number of hours by respectively 4, 9, 0 and 7 minutes.
Clearly my expectations of how these timezone offsets should work is not correct.
I tried again with simpler timezone names: pytz recognizes EST and CST, and yields the expected whole-hour offsets for them, but rejects MST and PST.
Now, I obtained these results on Windows 10, and I can already hear an answer along the lines of "Ah, well, Windows, everybody knows that timezones on Windows are bizarre" (which is true), but they are not that bizarre, and besides, I get results identical to the ones shown above from Python 3.8 on Linux.
What am I missing here?
It appears that the simplest solution is not to try and understand what pytz.tzinfo is doing, but instead move to zoneinfo.ZoneInfo that was added to the standard library in Python 3.9.

Datetime print time without offset

I am currently trying to convert times from UTC and the problem that i am having is that the offsets seem to be backwards. As you can see when i convert the UTC to EST, it shows an offset of -4:56 yet when i print the time, it seems to add 4:56 as opposed to the way it should be. I would really like to be able to convert a UTC time to any other timezone and have it display the local time there without the offset so the UTC here would be converted to something along the lines of 2019-03-06 9:12 EST.
>>> example.created
datetime.datetime(2019, 3, 6, 14, 8, 49, 841881, tzinfo=<UTC>)
>>> original_utc = example.created
>>> original_utc
datetime.datetime(2019, 3, 6, 14, 8, 49, 841881, tzinfo=<UTC>)
>>> conv_est = original_utc.replace(tzinfo=pytz.timezone('US/Eastern'))
>>> conv_est
datetime.datetime(2019, 3, 6, 14, 8, 49, 841881, tzinfo=<DstTzInfo 'US/Eastern' LMT-1 day, 19:04:00 STD>)
>>> print(conv_est)
2019-03-06 14:08:49.841881-04:56
>>> print(conv_est.astimezone())
2019-03-06 19:04:49.841881+00:00
I suspect that you misunderstood the method .astimezone().
Your original datetime is in UTC
>>> example.created
datetime.datetime(2019, 3, 6, 14, 8, 49, 841881, tzinfo=<UTC>)
Then you changed the timezone info for the variable conv_est, and indeed it works as designed:
>>> conv_est = original_utc.replace(tzinfo=pytz.timezone('US/Eastern'))
>>> conv_est
datetime.datetime(2019, 3, 6, 14, 8, 49, 841881, tzinfo=<DstTzInfo 'US/Eastern' LMT-1 day, 19:04:00 STD>)
If you print this variable, it shows the correct info
>>> print(conv_est)
2019-03-06 14:08:49.841881-04:56
But when you call .astimezone() without any argument, then the return value is a datetime object in UTC zone; that means the method is also working as designed, returning the same point in time but expressed as localtime in UTC (It will be 7PM/19hs in UTC when it is 2PM/14hs in US/Eastern).
>>> print(conv_est.astimezone())
2019-03-06 19:04:49.841881+00:00
You can test that yourself by calculating the difference (which will be 0):
>>> conv_est == conv_est.astimezone()
True
>>> conv_est - conv_est.astimezone()
datetime.timedelta(0)

Daylight time saving aware conversion of timestamps in python

Given a timestamp without time zone (e.g. 2018-03-12 09:30:00) AND the timezone EST5EDT, the goal is to parse the data returning a datetime object that is time zone AND daylight saving aware.
from datetime import datetime
import pytz
datetime(2018, 3, 8, 9, 30, tzinfo=pytz.timezone('EST5EDT')).astimezone(pytz.utc)
# returns:
# datetime.datetime(2018, 3, 8, 14, 30, tzinfo=<UTC>)
datetime(2018, 3, 12, 9, 30, tzinfo=pytz.timezone('EST5EDT')).astimezone(pytz.utc)
# returns:
# datetime.datetime(2018, 3, 12, 14, 30, tzinfo=<UTC>)
# BUT should return (second Sunday of march the daylight saving changes by 1 hour):
# datetime.datetime(2018, 3, 12, 13, 30, tzinfo=<UTC>)
Never set tzinfo directly when creating datetimes. Always use the localize() method of the timezone (see the note at the top of http://pytz.sourceforge.net/):
pytz.timezone('EST5EDT').localize(
datetime(2018, 3, 12, 9, 30)
).astimezone(pytz.utc)

timezone conversion in Python

I'm probably missing something about timezones:
>>> import datetime, pytz
>>> date = datetime.datetime(2013,9,3,16,0, tzinfo=pytz.timezone("Europe/Paris"))
>>> date.astimezone(pytz.UTC)
datetime.datetime(2013, 9, 3, 15, 51, tzinfo=<UTC>)
I was expecting
datetime.datetime(2013, 9, 3, 15, 00, tzinfo=<UTC>)
Can anyone explain me where these 51 minutes come from?
Thanks,
Jean-Philippe
The UTC offset gives (date.tzinfo.utcoffset(date)):
datetime.timedelta(0, 540)
This is 540 seconds or 9 minutes.
In France the switch to UTC was made on March 11, 1911 and the clocks were turned back 9 minutes and 21 seconds (source 1, source 2):
Until 1911, Paris was 9 minutes and 21 seconds off UTC.
You can also see it here (Paris time in 1911) where the time goes from March 11, 12:01:00 AM to March 10, 11:51:39 PM.
Read the note at the very begining of pytz documentation ; use .localize() method to create timezone-aware datetime object:
import datetime
import pytz
naive_dt = datetime.datetime(2013,9,3,16,0)
dt = pytz.timezone("Europe/Paris").localize(naive_dt, is_dst=None)
to_s = lambda d: d.strftime('%Y-%m-%d %H:%M:%S %Z%z')
print(to_s(dt))
print(to_s(dt.astimezone(pytz.utc)))
Output
2013-09-03 16:00:00 CEST+0200
2013-09-03 14:00:00 UTC+0000
I don't know why you are expecting 15:00 UTC here.
Thanks Simeon for your answer. It made me realize how shallow is my understanding of all of this. The following experimentations lost me a little more...
>>> import datetime, pytz
>>> date_paris = datetime.datetime(2013,9,3,16,0, tzinfo=pytz.timezone("Europe/Paris"))
>>> date_utc = datetime.datetime(2013,9,3,16,0, tzinfo=pytz.utc)
>>> date_paris.astimezone(pytz.utc)
datetime.datetime(2013, 9, 3, 15, 51, tzinfo=<UTC>)
>>> date_utc.astimezone(pytz.timezone("Europe/Paris"))
datetime.datetime(2013, 9, 3, 18, 0, tzinfo=<DstTzInfo 'Europe/Paris' CEST+2:00:00 DST>)
Why this 9 minutes offset shows up when converting in one direction but not the other? The following piece of code concentrate all disappointment:
>>> date_paris
datetime.datetime(2013, 9, 3, 16, 0, tzinfo=<DstTzInfo 'Europe/Paris' PMT+0:09:00 STD>)
>>> date_paris.astimezone(pytz.utc).astimezone(pytz.timezone("Europe/Paris"))
datetime.datetime(2013, 9, 3, 17, 51, tzinfo=<DstTzInfo 'Europe/Paris' CEST+2:00:00 DST>)

pytz.astimezone not accounting for daylight savings?

On 2013 Jun 1 I expect the "PST8PDT" timezone to behave like GMT+7, as it is daylight savings in that timezone. However, it behaves like GMT+8:
>>> import pytz, datetime
>>> Pacific = pytz.timezone("PST8PDT")
>>> datetime.datetime(2013, 6, 1, 12, tzinfo=Pacific).astimezone(pytz.utc)
datetime.datetime(2013, 6, 1, 20, 0, tzinfo=<UTC>)
In contrast, on 2013 Jan 1 it behaves (correctly) like GMT+8:
>>> datetime.datetime(2013, 1, 1, 12, tzinfo=Pacific).astimezone(pytz.utc)
datetime.datetime(2013, 1, 1, 20, 0, tzinfo=<UTC>)
What am I doing wrong? Thanks in advance!
You can't assign the timezone in the datetime constructor, because it doesn't give the timezone object a chance to adjust for daylight savings - the date isn't accessible to it. This causes even more problems for certain parts of the world, where the name and offset of the timezone have changed over the years.
From the pytz documentation:
Unfortunately using the tzinfo argument of the standard datetime constructors ‘’does not work’’ with pytz for many timezones.
Use the localize method with a naive datetime instead.
>>> Pacific.localize(datetime.datetime(2013, 6, 1, 12)).astimezone(pytz.utc)
datetime.datetime(2013, 6, 1, 19, 0, tzinfo=<UTC>)
>>> Pacific.localize(datetime.datetime(2013, 1, 1, 12)).astimezone(pytz.utc)
datetime.datetime(2013, 1, 1, 20, 0, tzinfo=<UTC>)

Categories