Translating WWVB time to local time

We’re almost at a point where I can share the final code. One of the minor hurdles I had to tackle was converting UTC time with a day of year value into a more standard Gregorian local time. It turns out that the TimeLord library doesn’t have the facilities for doing this conversion on it’s own – probably because it was designed to work with a real time clock module instead of a WWVB receiver with my clock code.

Below is the code I developed for doing the conversion. This function is much longer than I would have liked, and I try to keep things small so it’s easier to debug. We start off with a definitition of what time zone we’re in. I’m in GMT-5, so the definition goes as follows:

#define timezone -5

Also, we’ll need our definition of ‘struct time t’ which carries the UTC version of our current time:

struct TIME {
  uint8_t seconds;
  uint8_t minutes;
  uint8_t hours; 
  uint16_t doy; // day of year
  uint16_t year;
  uint8_t leapYear;
};

volatile struct TIME t; // our only global variable

If I recall correctly, those are the only definitions that are missing from the following code block. This code tries to generically calculate local time without the added issue of having to jump forward or back due to DST and it should work for all timezones. Note also that a good chunk of the code is trying to compensate for the local day/month/year changing due to crossing midnight on a particular day. I probably didn’t need to worry about that since the actual sunrise/sunset values will only change by about a minute from one day to the other. I guess I was being a little OCD when I wrote this.

// compute gregorian date from time struct t
void getGregorianDate(byte cdate[])
{
  uint8_t months[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
  uint16_t daysLeft = t.doy;
  uint8_t curMonth = 1;
  int8_t curHour, curDay, 
         curYear = t.year - 2000;
  
  if (t.leapYear == 0)
    months[1] = 28;
    
  while (daysLeft > months[curMonth-1])
  {
    daysLeft -= months[curMonth-1];
    curMonth++; 
  }
  
  // convert our stored UTC time to local time
  if (((int8_t) t.hours + timeZone) < 0)
  {
    // adjust for negative timeZone constant with rollovers 
    curHour = t.hours + 24 + timeZone;
    curDay = daysLeft - 1;
    
    if (curDay < 1) // roll back the month
    {
      // we're actually on the last day of the previous month
      curMonth--;
      curDay = months[curMonth - 1];
    }
    
    if (curMonth < 1) // roll back to prior year
    {
      curDay = months[11];
      curMonth = 12;
      curYear--;
    }
  }
  else // positive calculation with possible rollovers
  {    // note that even if Timezone is a negative constant, this code still works
    curHour = t.hours + timeZone;
    curDay = daysLeft;
    if (curHour > 23) // day
    {
      curHour -= 24;
      curDay++;
    }
    if (curDay > months[curMonth - 1]) // month
    {
      curDay = 1;
      curMonth++;
    }
    if (curMonth > 12) // year
    {
      curMonth = 1;
      curYear++;
    }
  }
  cdate[tl_second] = t.seconds;
  cdate[tl_minute] = t.minutes;
  cdate[tl_hour] = curHour;
  cdate[tl_day] = curDay;
  cdate[tl_month] = curMonth;
  cdate[tl_year] = curYear;
}

Could this be better? Absolutely. I just haven’t taken the time to go through and simplify it yet. There are too many variables being used, for one. I could eliminate all of the curDay, curMonth, and curYear references and use the cdate[] array in their place. I’m also not sure we really have to worry so much about whether the end result of adding the timezone results in a positive or negative value. There’s something there that makes me think I could possibly cut the code for calculating the day, month, and year in half, but the technique for doing so isn’t clear to me just yet. The one good thing I can say about it is that it works great in it’s current form, so I’m leaving it well enough alone for the moment.

Leave a comment