Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference of two timedelta objects, with timezones

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!

like image 624
Rolf Avatar asked Sep 07 '25 22:09

Rolf


1 Answers

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.

like image 58
unutbu Avatar answered Sep 09 '25 16:09

unutbu