Automatic Daylight Saving Time compensation for your clock projects

Pretty early in development of my Ping Pong Clock I came up with the idea of automatic Daylight Saving Time compensation. It’s an interesting feature, but it’s a luxury and so I figured I could add it as a future improvement. Now’s the time and I’m reporting back on what I’ve learned and how you can add this to your own projects.

There’s two things to think about before adding this feature. Is it worth the effort and does it make the clock more confusing rather than easier to use?

As to the latter, if you are responsible for setting the time initially but you are not responsible for resetting the clock when we fall back or spring forward will it cause confusion? Perhaps initially, but the battery-backed RTC that I used in my project should mean that you set it once and never have to reset it again. The one exception is DST and that’s what I’m compensating for.

Whether it is worth it or not is difficult to answer until after the fact. You should take into consideration that the DST rules are not set in stone, they change from time to time. Add to that the fact that not all parts of the world observe the practice. This means that not only do you need to implement the compensation, but you should add a method of switching it on and off as well as changing the rules for when it is observed.

Join me after the break to learn the method and code I use to make time adjustments automatically twice a year.

Implementation

There are two chunks that go into implementing DST compensation. The first part is to figure out if we are currently observing DST or if standard time is in force. The second portion of the problem is to develop a method to do the compensating without upsetting how the clock runs or how it is set.

This post will tackle the first problem only; how to decided when DST is in effect. I am planning a second post to detail the mechanics necessary to use that information.

The problem

Image source: Wikimedia Commons

Daylight Saving Time does not start on a set day, for instance April 15th of each year. Instead, it starts on a specific day of the week. The United States currently observes the start of DST at 2:00am on the second Sunday of April. The issue is further compounded by the fact that this legislated rule changes from time to time, most recently the US rules were changed in 2007. In order to perform compensation we must be able to answer the question: what is the exact date that DST starts?

We’ll need an algorithm that takes the day-of-week (DOW) based rules and translates them into an exact date answer. What is the minimum amount of input information necessary for the algorithm to still work? Let’s find out.

Working it out

If you were going to find the date when DST starts how would you do it? Given that DST begins on the second Sunday in April, what was the date that it started last year (2011)? Naturally, you would look at a calendar for April 2011, and count the Tuesdays until you get to the second one. That’s exactly what our algorithm will do, except it will not need to have a calendar in front of it. To solve our problem, let’s state explicitly the method we want to use:

  • Find the day-of-week for the first day in the given month.
  • Increment the date until you arrive at the target day-of-week.
  • increment weeks until you reach the target day.

The one useful piece of information that we get from looking at a calendar is what day of the week the month started on. Luckily, this is easily calculable and it’s just a quick Google search away.

Calculating Day-of-Week for any date

Wikipedia has a very nice article about calculating the day of the week. Much of the time spent in this calculation is used to establish if the year is a leap year or not. The rest of the calculation involves lookup tables to get to the final answer. Fortunately, DOW calculations are a common problem so there are several streamlined algorithms available for the task. I chose to use Sakamoto’s Algorithm because it’s already written in C and it’s quite simple.

int dow(int y, int m, int d)
   {
       static int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
       y -= m < 3;
       return (y + y/4 - y/100 + y/400 + t[m-1] + d) % 7;
   }

Explaining exactly how this works is an exercise you can save for a rainy day. When trying to work it out make sure you look up the rules of precedence. Note that “m < 3″ will be evaluated first, returning 0 if false and 1 if true. We’d also like to point out that the three division operations in the third line of code are used to adjust for leap years.

Finding the correct starting day

Now that we know the day of week for the first of the month, we need to find the date of the first occurrence of our target day. If we assign our target day to the variable “DOW”, day of week on the first day of the month to “firstDOW”, it’s simple to use a loop to increment until we get to our target:

char targetDate = 1;
  while (firstDOW != DOW){
    firstDOW = (firstDOW+1)%7;
    targetDate++;
  }

This code loop will exit when the variable “targetDate” matches the first occurrence of our target day. But this only solves a portion of the problem. We also need to identify the date for the second, third, or other occurrence of that day in the month.

Adjusting the target date to account for weeks

If we’re looking for the second Sunday in a given month, we can assign the number 2 to the variable “NthWeek”:

  targetDate += (NthWeek-1)*7;

This quick snippet will add seven to the date for each week after the first occurrence of our target day. Since I am subtracting one before multiplying by 7 (the number of days in a week) nothing will be added if we are looking for the first Sunday in the month.

Putting it all together

If we wrap all of our code into a nice little package we’ll end up with a function that returns the date based on input information. To keep focus on the problem, I first defined the information that will be passed into the function, and what I plan to get back from it:

  • Inputs: year, month, day-of-week (eg: Sunday = 0),  nth week of month (eg: the 2nd Sunday of the month = 2)
  • Output: an integer that represents the date (eg: 15 would be the 15th day of the month)

Because our first step relies on another algorithm it needs to be included in our package. I changed it just a little bit by replacing as many of the int data types with char as possible. Depending on the compiler you use this may end up saving on ram.

#include <stdio.h>
char buff[50];

int myYear = 2011;
char myMonth = 04;
char myDOW = 0;
char myNthWeek = 2;

/*--------------------------------------------------------------------------
  FUNC: 6/11/11 - Returns day of week for any given date
  PARAMS: year, month, date
  RETURNS: day of week (0-7 is Sun-Sat)
  NOTES: Sakamoto's Algorithm

http://en.wikipedia.org/wiki/Calculating_the_day_of_the_week#Sakamoto.27s_algorithm

    Altered to use char when possible to save microcontroller ram
--------------------------------------------------------------------------*/
char dow(int y, char m, char d)
   {
       static char t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
       y -= m < 3;
       return (y + y/4 - y/100 + y/400 + t[m-1] + d) % 7;
   }

/*--------------------------------------------------------------------------
  FUNC: 6/11/11 - Returns the date for Nth day of month. For instance,
    it will return the numeric date for the 2nd Sunday of April
  PARAMS: year, month, day of week, Nth occurence of that day in that month
  RETURNS: date
  NOTES: There is no error checking for invalid inputs.
--------------------------------------------------------------------------*/
char NthDate(int year, char month, char DOW, char NthWeek){
  char targetDate = 1;
  char firstDOW = dow(year,month,targetDate);
  while (firstDOW != DOW){
    firstDOW = (firstDOW+1)%7;
    targetDate++;
  }
  //Adjust for weeks
  targetDate += (NthWeek-1)*7;
  return targetDate;
}

int main(void){
  //Used to test on a computer
  sprintf(buff,"%i",NthDate(myYear,myMonth,myDOW,myNthWeek));
  printf("%s\n",buff);
}

I wrote this with the intent of using it on a microcontroller, but just for testing, I’ve included some I/O for feedback on  a computer. You can remove the first two lines and the entire main function and plop it into your project. On a Linux box compile it with GCC using: “gcc -o dst dst.c” and then run it using “./dst”.

Conclusion

This is a rather small amount of code which doesn’t require a lot of processing power to used. If you can add the option to set the current date to your clock project this is a snap to roll into the code. The one thing you need to work out is how to handle setting and storing the time. This will be different depending on whether or not DST is in effect.

As a side note. I ended up reusing this code for one of the Project Euler questions. I guess the work is already paying dividends.

Resources

Example code

Comments

  1. Jeff Epler says:

    For an unfinished GPS clock project, I wrote code to convert Unix timezone information to a table suitable for inclusion in my clock’s firmware; a 78-entry table (624-byte) table is enough to store the second of each DST change from 2000 to the end of the Unix epoch; it’s not unlikely that the DST rules in your area jurisdiction change in the next 20 years anyway (or that you’ll move to a jurisdiction with a different DST offset or rule in a similar length of time).

    One of the advantages of WWVB is that it carries two bits that help a clock apply DST at the right times. In the US, the user of a WWVB “atomic” clock need only indicate whether DST is observed in this jurisdiction, and the winter DST offset. As there are 4 time-zones in the lower 48, you could accomplish this with 3 DIP switches or jumpers. (I don’t think WWVB is received in Alaska, Hawaii, or Puerto Rico but I could be wrong; in that case, you’ll need 4 DIP switches)

    • selvkumarv says:

      How to find the DST calculation for the last sunday of March. How do give input for Last sunday.

      char NthDate(int year, char month, char DOW, char NthWeek)

      How do i represent the last sunday in ” NthWeek, ” ?
      E.g: In October 2011, there are 5 sundays.

  2. gregmac says:

    I have a clock that sets it self using WWVB, and has a button on the bottom that allows you to toggle DST. This solves several problems:

    * It allows easy (“one touch”) user-adjustment of DST without having to scroll through to set the time normally
    * If you don’t observe DST, you simply don’t touch the button
    * If the DST rules change it doesn’t matter, because the user has to press the button at the appropriate time anyway
    * The clock does not need to know the date
    * The clock does not need to know the DST rules
    * The clock does not need a way to update the DST rules (complex UI or network connectivity)

    Although not having to touch it at all is preferable to even a one-touch button, the extra complication of adjusting DST rules is not really worth it when you could just add a simple button. If you have network connectivity anyway, sure, have it auto-update the rules (though realize that whatever infrastructure it uses to do that must continue to exist for the lifetime of the clock).

    I really like the button as a simple, elegant and basically never obsolete solution.

  3. NewCommentor1283 says:

    i was never a fan of any kind of DST at all in any sense of the word, does that make my opingion of this DST issue biased? lolz yeh…

    anywho, i think the pingpong clock appearence is awesome!

    i’d suggest using a contrast enhancing colored (non diffused) display window…
    and make it “openable” for extra “night-light-ish-glow :) … or auto-brightness with override switch :)

  4. Geoffrey says:

    Good work.
    In the U.S., Daylight Saving Time starts the second Sunday of March and ends the first Sunday of November.

  5. Chetchez says:

    It’s Daylight Saving Time, not Daylight Savings Time

  6. echodelta says:

    There is nothing saving about it! For 30 plus years Indiana proves that we use more energy than if we leave well enough alone. This clock can’t display UTC. The glass clock before this post can. This is the way to hack at golf playing fat-cats in Washington and other eliteist types who play this old game in a 24/7 world.

  7. Roger Wilco says:

    DST is obsolete and we should do away with it. Individuals who might need to adjust can do it (get up earlier or later depending on time of year) there is no reason for a whole country to change time causing confusion

    • JB says:

      Exactly. DST causes more car accidents too. If anyone needs to get up earlier then do so, but don’t bother the rest of the country for it. Whatever claimed “saving” is done away with if you have to get up earlier and turn on the lights because it is still dark outside. Stupid regulation!

  8. Ash says:

    Great work! On a different note – does the daylight really saves energy??

  9. alex says:

    hi, great work, but if i include in arduino sketch?

  10. SuperFabius says:

    Hi all,
    using some modular arithmetics is possible optimize Sakamoto’s algorithm for 8 bit MCU, using only one byte variables and limiting year in the range [2000-2099]:

    dow = (yy1 + (yy1+3)/4 + t[month-1] + day) MOD 7

    where: yy1 [5-104] = year-1995 (year from 2000 to 2099) or: yy1 [5-104] = year+5 (year from 00 to 99)
    month [1-12]
    day [1-31]
    / is the integer division
    dow [0-6] (Sun=0, Mon=1, … Sat=6)

    Here is an example of an assembler implementation for a 16F Midrange PIC:

    ;*******************************************
    ;Input:
    ; day [1-31] -> RTC1_SYSday
    ; month [1-12] -> RTC1_SYSmth
    ; year [0-99] -> RTC1_SYSyar
    ;
    ;
    ;Output: day of week [0-6] -> W
    ;*******************************************
    RTC1_dow_var UDATA

    RTC1_SYSday res 1
    RTC1_SYSmth res 1
    RTC1_SYSyar res 1
    RTC1_yy1 res 1
    RTC1_yy2 res 1

    RTC1_dow_code CODE

    Sub_RTC1_dow

    ;calculate yy1=year+5
    banksel RTC1_yy1
    movlw d’5′
    addwf RTC1_SYSyar,w ;W=year+5
    movwf RTC1_yy1 ;yy1=year+5

    ;if month yy1=yy1-1
    movf RTC1_SYSmth,w
    addlw -3
    btfss STATUS,C ;month>=3 (C=1)?
    decf RTC1_yy1,f ;no, yy1=yy1-1
    ;yes, continue

    ;calculate yy2=(yy1+3)/4 (note: / is integer div)
    movf RTC1_yy1,w
    addlw d’3′ ;W=yy1+3 (note: yy1 [7-104], so always C=0)
    movwf RTC1_yy2 ;yy2=yy1+3
    rrf RTC1_yy2,f ;yy2=(yy1+3)/2
    bcf STATUS,C ;clear C
    rrf RTC1_yy2,f ;yy2=(yy1+3)/2/2=(yy1+3)/4

    ;calculate W=t(month-1)
    call RTC1_dow_t ;W=t(month-1)
    ;add all terms
    addwf RTC1_SYSday,w ;W=t(month-1)+day
    addwf RTC1_yy2,w ;W=t(month-1)+day+yy2
    addwf RTC1_yy1,w ;W=t(month-1)+day+yy2+yy1

    ;calculate W mod 7
    RTC1_dow_Wmod7
    addlw -d’7′ ;W=W-7
    btfsc STATUS,C ;W>=7 (C=1)?
    goto RTC1_dow_Wmod7 ;yes
    ;no, continue (W=W-(W/7)*7-7)
    addlw d’7′ ;W=W-(W/7)*7-7+7=W-(W/7)*7=W mod 7
    return

    RTC1_dow_t ;calculate W=t(month-1)
    movlw HIGH RTC1_dow_tab
    movwf PCLATH ;PCLATH=RTC1_dow_tab(b12-b8)
    decf RTC1_SYSmth,w ;W=month-1 (note: month-1 [0-11])
    addlw LOW RTC1_dow_tab;W=month-1+RTC1_dow_tab(b7-b0)
    btfsc STATUS,C ;0xFF boundary crossed (C=1)?
    incf PCLATH,f ;yes, adjust PCLATH
    ;no, continue
    movwf PCL ;jump to table RTC1_dow_tab (retlw)

    ;t(month-1) table
    RTC1_dow_tab dt 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4
    ;*******************************************

    Cheers.

  11. SuperFabius says:

    I forget in the dow formula to say that if month<3 than yy1=yy1-1.
    Assembler example already takes this into account.

    Cheers.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 96,672 other followers