Difference of two timedelta objects, with timezones - python

I would like to calculate how many hours there are in a date interval: for example "2014.03.29-30" should give 47, because of the daylight savings.
My method is making two datetime objects, in the example the following:
datetime.datetime(2014, 3, 29, 0, 0, tzinfo=<DstTzInfo 'Europe/Budapest' LMT+1:16:00 STD>)
datetime.datetime(2014, 3, 30, 23, 59, tzinfo=<DstTzInfo 'Europe/Budapest' LMT+1:16:00 STD>)
return (date2-date1) + timedelta(minutes=1)
However, it gives "2 days, 0:00:00", which is not correct. How could I make a timedelta object which takes timezones and dst into account? Also, if there's a simpler solution for the whole problem, I'm open to it.
Thank you!

Before 1901-12-13 20:45:52 UTC, the 'Europe/Budapest' timezone was LMT+1:16:00 STD.
Currently, as of 2016-05-05, the 'Europe/Budapest' timezone is CET+2:00:00 DST.
If you use pytz's localize method, then pytz will choose the timezone (utcoffset and dstoffset) for 'Europe/Budapest' which is appropriate for the given naive datetime:
import datetime as DT
import pytz
tzone = pytz.timezone('Europe/Budapest')
date1 = tzone.localize(DT.datetime(2014, 3, 29, 0, 0), is_dst=None)
# datetime.datetime(2014, 3, 29, 0, 0, tzinfo=<DstTzInfo 'Europe/Budapest' CET+1:00:00 STD>)
In contrast, if you supply tzinfo=tzone directly to datetime.datetime, as in:
wrong_date1 = datetime.datetime(2014, 3, 29, 0, 0, tzinfo=tzone)
# datetime.datetime(2014, 3, 29, 0, 0, tzinfo=<DstTzInfo 'Europe/Budapest' LMT+1:16:00 STD>)
then the datetime.datetime incorrectly chooses the very first timezone associated with 'Europe/Budapest' regardless of whether or not that was the timezone in effect on 2014-3-29.
Therefore, when using pytz, always use tzone.localize to make naive datetimes timezone-aware:
import datetime as DT
import pytz
tzone = pytz.timezone('Europe/Budapest')
date1 = tzone.localize(DT.datetime(2014, 3, 29, 0, 0), is_dst=None)
date2 = tzone.localize(DT.datetime(2014, 3, 30, 23, 59), is_dst=None)
print(((date2-date1) + DT.timedelta(minutes=1)).total_seconds()/3600.)
# 47.0
Do not use tzinfo=tzone unless tzone is pytz.utc (or a timezone which is alway the same throughout its history.)
Where did the date 1901-12-13 20:45:52 UTC come from?
You can peek at a pytz timezone's utc transition times (and associated transition info) using its tzone._utc_transition_times and tzone._transition_info private attributes:
In [43]: [(utcdate, utcoffset, dstoffset, tzabbrev) for utcdate, (utcoffset, dstoffset, tzabbrev) in zip(tzone._utc_transition_times, tzone._transition_info)][:2]
Out[43]:
[(datetime.datetime(1, 1, 1, 0, 0),
datetime.timedelta(0, 4560),
datetime.timedelta(0),
'LMT'),
(datetime.datetime(1901, 12, 13, 20, 45, 52),
datetime.timedelta(0, 3600),
datetime.timedelta(0),
'CET')]
This shows that from the date 1-1-1 UTC to 1901-12-13 20:45:52 UTC the timezone abbreviation was LMT and the utcoffset was 4560 seconds, which equals 1 hour and 16 minutes:
In [47]: print(DT.timedelta(0, 4560))
1:16:00
Hence the first timezone associated with 'Europe/Budapest' is LMT+1:16:00 STD.

Related

datetime.combine with timezone is different from datetime.now with timezone

In the below code:
from datetime import datetime
import pytz
EDT = pytz.timezone('US/Eastern')
d1 = datetime.now(tz=EDT)
d2 = datetime.combine(d1.date(), d1.time(), tzinfo=EDT)
Why are d1 and d2 showing different timezone information?
>> d1
datetime.datetime(2021, 4, 8, 7, 0, 44, 316514, tzinfo=<DstTzInfo 'US/Eastern' EDT-1 day, 20:00:00 DST>)
>> d2
datetime.datetime(2021, 4, 8, 7, 0, 44, 316514, tzinfo=<DstTzInfo 'US/Eastern' LMT-1 day, 19:04:00 STD>)
How do I get the same datetime as datetime.now when using datetime.combine?
datetime.now effectively converts (localizes) your datetime with the pytz timezone object - from the docs:
If tz is not None, it must be an instance of a tzinfo subclass, and
the current date and time are converted to tz’s time zone.
datetime.combine does not do that. It is as if you would write something like datetime(2020,1,1, tzinfo=pytz.timezone('US/Eastern')) - effectively not adjusting the time zone to the provided date/time. See also e.g. pytz localize vs datetime replace and pytz: The Fastest Footgun in the West for more background info.
The correct way to get d2 with pytz would be
d2 = EDT.localize(datetime.combine(d1.date(), d1.time()))
No such issues if using timezone objects from dateutil or zoneinfo (Python 3.9+):
from datetime import datetime
from zoneinfo import ZoneInfo
EDT = ZoneInfo('US/Eastern')
d1 = datetime.now(tz=EDT)
d2 = datetime.combine(d1.date(), d1.time(), tzinfo=EDT)
# d1
# Out[75]: datetime.datetime(2021, 4, 8, 7, 57, 18, 309209, tzinfo=zoneinfo.ZoneInfo(key='US/Eastern'))
# d2
# Out[76]: datetime.datetime(2021, 4, 8, 7, 57, 18, 309209, tzinfo=zoneinfo.ZoneInfo(key='US/Eastern'))
# d1 == d2
# Out[77]: True

datetime.now(tz) and datetime(year, month, day, tzinfo=tz) don't have same UTC offset

Why are the UTC offsets in the 2 datetimes created in the code below different? I'm using pytz==2019.1
from datetime import datetime
import pytz
EASTERN = pytz.timezone('US/Eastern')
dt1 = datetime.now(EASTERN)
dt2 = datetime(2020, 8, 11, 15, 30, tzinfo=EASTERN)
print(f'dt1: {dt1}')
print(f'dt2: {dt2}')
results:
dt1: 2020-08-11 18:35:47.984745-04:00
dt2: 2020-08-11 15:30:00-04:56
The first one shows an UTC offset of -04:00 which is correct (for this time of year), but the 2nd one is giving an UTC offset of -04:56.
How can I declare a datetime with the 2nd method and get the UTC offset from the first method.
The issue with the shown code is that datetime.now(tz) can handle a pytz.timezone as tz argument while the default constructor datetime(y,m,d,...) cannot. In this case, you have to use the localize method of the timezone,
from datetime import datetime
import pytz
EASTERN = pytz.timezone('US/Eastern')
dt1 = datetime.now(EASTERN)
dt2 = EASTERN.localize(datetime(2020, 8, 11, 15, 30))
print(f'dt1: {dt1}')
print(f'dt2: {dt2}')
# prints
# dt1: 2020-08-12 10:07:09.738763-04:00
# dt2: 2020-08-11 15:30:00-04:00
dateutil avoids this issue, more info can be found here. That would make the code work "as it is":
from dateutil.tz import gettz
EASTERN = gettz('US/Eastern')
dt1 = datetime.now(EASTERN)
dt2 = datetime(2020, 8, 11, 15, 30, tzinfo=EASTERN)
print(f'dt1: {dt1}')
print(f'dt2: {dt2}')
# prints e.g.
# dt1: 2020-08-12 10:20:07.796811-04:00
# dt2: 2020-08-11 15:30:00-04:00
In addition pytz is likely to be deprecated with Python 3.9 as you'll have zoneinfo as part of the standard library for these kinds of jobs.
with regards to dt1:
datetime.now() will return the tzinfo from utc. You can see this if you do the following:
>>> repr(EASTERN.fromutc(datetime.now()))
"datetime.datetime(2020, 8, 11, 12, 33, 28, 849873, tzinfo=<DstTzInfo 'US/Eastern' EDT-1 day, 20:00:00 DST>)"
the tz.fromutc() is what datetime.now() does behind the scenes.
with regards to dt2:
if you check the repr of EASTERN you will see the following:
>>> repr(EASTERN)
"<DstTzInfo 'US/Eastern' LMT-1 day, 19:04:00 STD>"
>>> repr(dt2)
"datetime.datetime(2020, 8, 11, 15, 30, tzinfo=<DstTzInfo 'US/Eastern' LMT-1 day, 19:04:00 STD>)"
I'm not very comfortable with timezone differences so I don't want to steer you in the wrong direction, but from what I can tell both of these two options yield the same result:
dt2 = datetime(2020, 8, 11, 15, 30, tzinfo=EASTERN)
dt2_eastern_corrected = EASTERN.fromutc(dt2)
dt3 = datetime(2020, 8, 11, 15, 30)
dt3_eastern_corrected = EASTERN.fromutc(dt3
>>> repr(dt2_eastern_corrected)
"datetime.datetime(2020, 8, 11, 11, 30, tzinfo=<DstTzInfo 'US/Eastern' EDT-1 day, 20:00:00 DST>)"
>>> repr(dt3_eastern_corrected)
"datetime.datetime(2020, 8, 11, 11, 30, tzinfo=<DstTzInfo 'US/Eastern' EDT-1 day, 20:00:00 DST>)"

Python astimezone() unexpected result

Given a variable containing the datetime of 2000-01-01 00:01 in Paris timezone (UTC+2 in winter afaik):
datetime.datetime(2000, 1, 1, 0, 1, tzinfo=pytz.timezone('Europe/Paris'))
I expected the conversion to UTC to result in a datetime of 1999-12-31 22:01, but got instead:
datetime.datetime(2000, 1, 1, 0, 1, tzinfo=pytz.timezone('Europe/Paris')).astimezone(pytz.utc)
datetime.datetime(1999, 12, 31, 23, 52, tzinfo=<UTC>)
What am I missing ?
Thanks
Unfortunately using the tzinfo argument of the standard datetime
constructors ‘’does not work’’ with pytz for many timezones.
>>> datetime(2002, 10, 27, 12, 0, 0, tzinfo=amsterdam).strftime(fmt)
'2002-10-27 12:00:00 LMT+0020'
It is safe for timezones without daylight saving transitions though, such as UTC:
>>> datetime(2002, 10, 27, 12, 0, 0, tzinfo=pytz.utc).strftime(fmt)
'2002-10-27 12:00:00 UTC+0000'
As you'll notice:
>>> datetime.datetime(2000, 1, 1, 0, 1, tzinfo=pytz.timezone('Europe/Paris'))
datetime.datetime(2000, 1, 1, 0, 1, tzinfo=<DstTzInfo 'Europe/Paris' LMT+0:09:00 STD>)
"LMT+0:09:00 STD"…?! That's a historical offset, not a current standard.
The timezone bundles (containing all historical offsets since forever) returned by pytz aren't handled correctly by datetime, and it chooses some random (well, the first probably) offset instead of the offset pertinent to the actual time. Arguably, since it needs to interpret the time correctly first it cannot choose the right offset by time from the timezone bundle.
This library only supports two ways of building a localized time. The
first is to use the localize() method provided by the pytz library.
This is used to localize a naive datetime (datetime with no timezone
information):
>>> loc_dt = eastern.localize(datetime(2002, 10, 27, 6, 0, 0))
>>> print(loc_dt.strftime(fmt))
2002-10-27 06:00:00 EST-0500
The second way of building a localized time is by converting an existing
localized time using the standard astimezone() method:
>>> ams_dt = loc_dt.astimezone(amsterdam)
>>> ams_dt.strftime(fmt)
'2002-10-27 12:00:00 CET+0100'
http://pytz.sourceforge.net

Get UNIX timestamp (ie UTC timestamp) from a Python timezone aware date

My computer is running in Pacific time (hence datetime.datetime.fromtimestamp(0) gives me 1969-12-31 16:00:00). My problem is that given a timezone aware datetime object in Python, I want to get the UNIX timestamp (ie the UTC timestamp). What is the best way to do so?
import calendar
import datetime
import pytz
d = datetime.datetime.now(pytz.timezone('America/Los_Angeles'))
# d == datetime.datetime(2012, 7, 10, 1, 6, 36, 37542, tzinfo=<DstTzInfo 'America/Los_Angeles' PDT-1 day, 17:00:00 DST>)
ts = calendar.timegm(d.utctimetuple())
# ts == 1341907596
# test with UTC epoch:
d = datetime.datetime(1969, 12, 31, 16, 0, 0, 0, pytz.timezone('America/Los_Angeles'))
# d == datetime.datetime(1969, 12, 31, 16, 0, tzinfo=<DstTzInfo 'America/Los_Angeles' PST-1 day, 16:00:00 STD>)
ts = calendar.timegm(d.utctimetuple())
# ts == 0

inconsistency when switching between timezones in python

I have a datetime object created like this:
tm = datetime.datetime.strptime('2010 Aug 04 14:15:16', '%Y %b %d %H:%M:%S')
>>> tm
datetime.datetime(2010, 8, 4, 14, 15, 16)
I then set the timezone like this:
tm.replace(tzinfo=pytz.timezone('UTC'))
>>> tm
datetime.datetime(2010, 8, 4, 14, 15, 16, tzinfo=<UTC>)
Eventually, I change the timezone to US/Pacific like this:
>>> tm = tm.astimezone(pytz.timezone('US/Pacific'))
>>> tm
datetime.datetime(2010, 8, 4, 7, 15, 16, tzinfo=<DstTzInfo 'US/Pacific' PDT-1 day, 17:00:00 DST>)
This is correct. But if I were to go into the other direction:
>>> tm = tm.replace(tzinfo =pytz.timezone('US/Pacific'))
>>> tm
datetime.datetime(2010, 8, 4, 14, 15, 16, tzinfo=<DstTzInfo 'US/Pacific' PST-1 day, 16:00:00 STD>)
As you can see, using replace() with the PST timezone incorrectly assumes it is not daylight savings time. Therefore, when I convert to UTC, it adds 8 hours instead of 7.
Why is it doing this and how do I change it so it does what I want (correctly realize DST or not)?
Yes, this is why you use .astimezone and not .replace when you have a datetime with a timezone. Using .astimezone gives the timezone a chance to adjust for things like daylight savings. Only use .replace to give a naïve datetime a tzinfo object.

Categories

Resources