pythondatetimetimezonetimedeltatzinfo

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!


Solution

  • 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.