Search code examples
pythondatetimetimezonepytz

List Daylight Saving start and finish over past x years


Is there a way to extract the beginning and end of Daylight Saving Time for a given timezone (in this case London), for a given year(s).

I need the beginning and end of daylight saving in London over the past x years in a DateTime format.

e.g. if years = 3

2016        27 March 02:00      30 October 02:00

2017        26 March 02:00      29 October 02:00

2018        25 March 02:00      28 October 02:00

Is there a way to extract this automatically from a python library, or should I just look it up and create my own list?


Solution

  • Here's a function that uses a binary search to locate the minute that marks the transitions to/from daylight saving time. It doesn't rely on any any secret internal data structures.

    import pytz
    import datetime
    
    def middle_date(d1, d2):
        diff = d2 - d1
        minutes = diff.total_seconds() // 60
        if minutes < 48 * 60:
            return d1 + datetime.timedelta(0, (minutes // 2) * 60)
        days = diff.days
        return d1 + datetime.timedelta(days // 2)
    
    utc = pytz.utc
    
    def saving_start_end(year, tz):
        t1 = datetime.datetime(year, 1, 1, tzinfo=tz).astimezone(utc)
        t4 = datetime.datetime(year, 12, 31, 23, 59, tzinfo=tz).astimezone(utc)
        t2 = t3 = middle_date(t1, t4)
        one_minute = datetime.timedelta(0, 60)
        dst_start = t1.astimezone(tz).dst()
        if dst_start == t2.astimezone(tz).dst():
            t2 = None
        else:
            while t1 < t2:
                mid = middle_date(t1, t2)
                if mid.astimezone(tz).dst() == dst_start:
                    t1 = mid + one_minute
                else:
                    t2 = mid
            t2 = (t2 - one_minute).astimezone(tz) + one_minute
        dst_mid = t3.astimezone(tz).dst()
        if dst_mid == t4.astimezone(tz).dst():
            t4 = None
        else:
            while t3 < t4:
                mid = middle_date(t3, t4)
                if mid.astimezone(tz).dst() == dst_mid:
                    t3 = mid + one_minute
                else:
                    t4 = mid
            t4 = (t4 - one_minute).astimezone(tz) + one_minute
        return t2, t4
    

    Tests:

    >>> central = pytz.timezone('America/Chicago')
    >>> for dt in saving_start_end(2019, central): print(dt.isoformat(' '))
    
    2019-03-10 02:00:00-06:00
    2019-11-03 02:00:00-05:00
    >>> london = pytz.timezone('Europe/London')
    >>> for year in (2016, 2017, 2018):
        start, end = saving_start_end(year, london)
        print(start.isoformat(' '), end.isoformat(' '))
    
    2016-03-27 01:00:00+00:00 2016-10-30 02:00:00+01:00
    2017-03-26 01:00:00+00:00 2017-10-29 02:00:00+01:00
    2018-03-25 01:00:00+00:00 2018-10-28 02:00:00+01:00