Cunning Chronograph: Outfoxed by Daylight Saving Time
I must admit to finding mself a tad disgruntled yesterday morning. As I was meandering across the bay outside my office on the way to the kitchen to grab a well-earned mug of coffee, I happened to glance at my Cunning Chronograph to bask in the glow of its display, only to discover that it was off by an hour.
(Source: Max Maxfield / Embedded.com)
As a simple rule of thumb, when anything happens like this it's usually my fault, so my knee-jerk reaction was to assume that something had gone wrong with my Arduino code. The first thing I did was to cycle the power to the Cunning Chronograph, but to no avail. Then I remembered that we'd entered Daylight Saving Time (DST) the day before, which meant that the clocks had moved one hour forward. Dagnabbit! I had forgotten all about this, which prompted some gnashing of teeth and rending of garb, let me tell you.
As always, of course, there are a variety of potential solutions. Perhaps the simplest would be to add a menu entry to the Simblee-based iPad app I use to control the Cunning Chronograph (see Controlling IoT devices with mobile platforms -- Creating hierarchical menus and Cunning Chronograph: Migrating to custom boards).
This really would take only a few seconds, and all it means is that I'd have to change the time on the Cunning Chronograph by hand twice a year, just like I have to do on the stove and the microwave and my bedside alarm clock and my car dashboard (to name but a few), but...
...then we have my chum, Bob, to contend with. Bob is a master carpenter. He's the one who made the Cunning Chronograph's cabinet with its hand-carved Celtic knot for me. He's also making one for himself. My cabinet is stained black with silver gilt; Bob is planning to make his a dark cherry color with antiqued brass gilt. Bob made the cabinets -- I'm furnishing the electronics.
Now, I'm sure that Bob would be in no way perturbed if I told him that he had to occasionally change the time on his Cunning Chronograph to accommodate DST. On the other hand, I would much prefer not to even mention this to him. Once we've finished his clock, when the time changes in the future, I'd like him to come into his workshop, glance at his Chronograph, and realize that it's changed the time automatically, at which point he'll think "Max, you clever little rascal!"
What we need is an auspicious algorithm. In the case of Alabama USA, which is where I currently hang my hat, the time advances by one hour on the second Sunday in March and it is set back by one hour on the first Sunday in November ("Spring Forward, Fall Back," as the saying goes). More specifically, these one-hour shifts occur at 2:00 a.m. local time. In spring, the clock jumps forward from the last moment of 1:59 a.m. standard time to 3:00 a.m. DST and that day has 23 hours; in autumn, the clock jumps backward from the last moment of 1:59 a.m. DST to 1:00 a.m. standard time, repeating that hour, and that day has 25 hours.
The real-time-clock (RTC) module my Cunning Chronograph uses to keep track of time is the ChronoDot from Adafruit. Based on the DS3231 temperature-compensated RTC, the ChronoDot is accurate to around a minute a year, which I think is pretty darned good.
RTClib library recommended by Adafruit, we can easily access the following integer values:
year (2016, for example) month (1 to 12) day (1 to 31) hour (0 to 23) minute (0 to 59) second (0 to 59)
This gives us a pretty good start, but how do we now work out if we're between 2:00 a.m. on the second Sunday of March and 2:00 a.m. on the first Sunday of November?
I had a quick Google (while no one was looking) and found this Determination of the day of the week page on the Wikipedia. Assuming
d represent the current year, month, and day, respectively, then the following expression will return an integer value for
dow (day of the week), where 0 = Sunday, 1 = Monday, 2 = Tuesday, etc. (note that the values of
d are not preserved):
dow = (d+=m<3?y--:y-2,23*m/9+d+4+y/4-y/100+y/400)%7;
Well, this looks promising -- it even accounts for leap years. From our RTC, we know the current year, month, day, hour, minute, and second. Using the above formula, we can determine what day of the week it is. Now all we need is a way to munge all of this information together to determine if we're between 2:00 a.m. on the second Sunday of March and 2:00 a.m. on the first Sunday of November, in which case we add an hour to whatever value we're getting from the RTC.
The answer is obvious; all we have to do is... but no, why should I have all the fun? Let's see who can come up with the most capriciously cunning function called
isDST () that accepts integer arguments of
(year, month, day, hour, minute, second) and returns a Boolean value of
true if we are indeed currently in Daylight Saving Time.
"But how shall we determine which offering is the most capriciously cunning?" I hear you cry. Well, all I can say is that we'll know it when we see it.