Maketober Day 12: Fun with British Summer Time

I’m trying to make something every day. Or at least start making something every day. Or perhaps finish something that I started a while back. Or maybe start finishing something. Like a timed night light I started a while back. I thought I’d move the code to MicroPython (the device is ESP8266 based). The idea is that the light changes colours at different times. I built the lightbox a while back.

To make it work properly I have to get the time, which is easy enough in MicroPython. But I also have to deal with British Summer Time. Which is a bit of a pain. In summer time the clocks go forward to make the most of daylight and in the winter they go back again. I don’t want my light to get this wrong, so my code has to check for summer time and and add an hour to the time value if required. I’ve written this code many times. In a previous life I had my software putting best by dates and times on all kinds of things, from Budweiser beer to Cadbury chocolates to windscreen wipers. However, I’ve never written a version in Python. Here’s the code I ended up with:

def summerTime(time):
    year=time[YEAR]
    month = time[MONTH]
    day = time[DAY]
    hour = time[HOUR]
    print("Test: ", year,month,day,hour)
    if month<3 or month>10: return False
    if month>3 and month<10: return True
    if month==3:
        hours_into_March = hour + (24 * day)
        date_of_Sunday = 31 - ((year + int(year/4) + 4) % 7)
        summertime_start = 2 + 24*date_of_Sunday
        print("   Date of Sunday: ", date_of_Sunday)
        print("   Hours into March: ", hours_into_March)
        print("   Summertime start: ", summertime_start)
        if hours_into_March>=summertime_start: return True
    if month==10:
        hours_into_October = hour + (24 * day)
        date_of_Sunday = 31 - ((year + int(year/4) + 1) % 7) 
        summertime_end = 2 + 24*date_of_Sunday
        print("   Date of Sunday: ", date_of_Sunday)
        print("   Hours into October: ", hours_into_October)
        print("   Summertime end: ", summertime_end)
        if hours_into_October<summertime_end: return True
    return False

I give this function a time value made up of a tuple that contains six values. I’ve made some constants that I can use to pull each element out of the tuple.

# offsets into the time 
YEAR=0
MONTH=1
DAY=2
HOUR=3
MIN=4
SEC=5

The code is heavily instrumented (i.e. it prints out the values as it works things out). I can comment those statements out afterwards. Once I’ve written the code then came up with a way of testing it:

def testTime():
    test_data = ( ((2020, 10, 25, 1, 30, 0), True),
                 ((2020, 10, 25, 2, 00, 0), False),
                 ((2021, 03, 28, 1, 00, 0), False),
                 ((2021, 03, 28, 2, 00, 0), True),
                 ((2021, 10, 31, 1, 30, 0), True),
                 ((2021, 10, 31, 2, 00, 0), False),
                 ((2021, 07, 23, 0, 30, 0), True))
    for test in test_data:
        if summerTime(test[0]) == test[1]:
            print("Test passed", test)
        else:
            print("****Test failed: ", test)

The test data is a date tuple and whether or not the date is in summer time. I’ve focused on the boundary conditions since these are when things can go wrong. If I was doing this for money I’d have a lot more tests than these. It seems to work (at least with these tests) so I can put it into my code.

Test:  2020 10 25 1
   Date of Sunday:  25
   Hours into October:  601
   Summertime end:  602
Test passed ((2020, 10, 25, 1, 30, 0), True)
Test:  2020 10 25 2
   Date of Sunday:  25
   Hours into October:  602
   Summertime end:  602
Test passed ((2020, 10, 25, 2, 0, 0), False)
Test:  2021 3 28 1
   Date of Sunday:  28
   Hours into March:  673
   Summertime start:  674
Test passed ((2021, 3, 28, 1, 0, 0), False)
Test:  2021 3 28 2
   Date of Sunday:  28
   Hours into March:  674
   Summertime start:  674
Test passed ((2021, 3, 28, 2, 0, 0), True)
Test:  2021 10 31 1
   Date of Sunday:  31
   Hours into October:  745
   Summertime end:  746
Test passed ((2021, 10, 31, 1, 30, 0), True)
Test:  2021 10 31 2
   Date of Sunday:  31
   Hours into October:  746
   Summertime end:  746
Test passed ((2021, 10, 31, 2, 0, 0), False)
Test:  2021 7 23 0
Test passed ((2021, 7, 23, 0, 30, 0), True)

Above is the test output. Again, if I was serious about this I’d have made a bunch of unit tests. However, this works for my purposes. It’s worth noting that having automated tests made it much easier to debug the function when it misbehaved.

When I first tried the code I had written it failed and it took me a few minutes to work out why. It was because I was taking some C++ code and turning it into Python. And it has to do with modulo. You can use this to get the remainder of a divide operation. So any number modulo 7 would have the range 0 to 6. The code above uses this to calculate an offset into week.

Python has a modulo operator but it has an important difference from the C++ one. The C++ % operator only works on integers. But the Python % operator will work on floating point values. So 10.5 % 7 would be 3.5. You can argue which is the “best” way to do it, but for me the importance of the difference is that it broke the C++ code I was translating into Python. I had to re-write the code to allow for this.