Advertisements

Archive for October 14th, 2009

Converting Day-of-Year to Month and Day-of-Month, Then Back Again

I needed the conversions for a WWVB simulator, so it knows when to twiddle the leap-year and leap-second bits.

The general notion is a table with the Day-of-Year values for the last day of each month. The most expedient way of doing this is with two columns: one for normal years and the other for leap years, thusly…

Month EOM EOM-LY DOY DOY-LY
0 0 0 0 0
1 31 31 31 31
2 28 29 59 60
3 31 31 90 91
4 30 30 120 121
5 31 31 151 152
6 30 30 181 182
7 31 31 212 213
8 31 31 243 244
9 30 30 273 274
10 31 31 304 305
11 30 30 334 335
12 31 31 365 366

Hint: even if you can recite the “Thirty days hath November …” jingle, it’s much better to build a spreadsheet so the additions all work out. It’s even better if you don’t attempt any of this with a late-summer head cold. You might want to check all my work, because I’m still stuffy.

With table in hand, the code is straightforward.

Define a structure with all the various bits & pieces of the current time, much of which isn’t used here. It’s all needed in the WWVB simulator:

enum EVENTSTATE {EVENT_INACTIVE,EVENT_PENDING,EVENT_ACTIVE};

struct timecode_ {			// The current moment in time...
  byte Year;				// 0 - 99
  word DayOfYear;			// 1 - 366
  byte Hour;				// 0 - 23
  byte Minute;				// 0 - 59
  byte Second;				// 0 - 60 (yes!)
  byte Tenth;				// 0 - 9
  enum EVENTSTATE LeapYear;		// 0 = no, 1 = pending, 2 = active
  enum EVENTSTATE LeapSecond;		// 0 = no, 1 = pending, 2 = active in this minute
  enum EVENTSTATE DaylightSavingTime;	// 0 = no, 1 = pending, 2 = active
  char UT1Correction;			// 100 ms units, -10 to +10 range (+/- 1 second)
  byte MinuteLength;			// 60 or 61
  byte Month;				// 1 - 12 (not sent in frame)
  byte DayOfMonth;			// 1 - 28..31	(not sent in frame)
};

That’s obviously overspecified, because DayOfYear with LeapYear uniquely determines Month and DayOfMonth. It’s handy to have both forms around, sooo there they are.

 

Then set up the table, glossing over the quick matrix transposition that turns the entries for each year into rows rather than columns:

prog_uint16_t PROGMEM MonthEnds[2][13] = {
  0,31,59,90,120,151,181,212,243,273,304,334,365,
  0,31,60,91,121,152,182,213,244,274,305,335,366
};

Conversion from DayOfYear to Month and DayOfMonth requires searching backwards through the appropriate table row until you find the entry that’s smaller than the DayOfYear value, at which point you’ve found the right month.

void ConvertDOYtoDOM(struct timecode_ *pTime) {
byte Index,LY;
word EndOfMonth;
  LY = (EVENT_INACTIVE != pTime->LeapYear) ? 1 : 0;
  Index = 12;
  while ((EndOfMonth = pgm_read_word(&MonthEnds[LY][Index])) >= pTime->DayOfYear) {
	--Index;
  };
  pTime->Month = Index + 1;									// months start with 1, not 0
  pTime->DayOfMonth = (byte)(pTime->DayOfYear - EndOfMonth);
}

Converting from Month and DayOfMonth to DayOfYear is much easier, as it’s pretty much just a table lookup:

word ConvertDOMtoDOY(struct timecode_ *pTime) {
word EndOfMonth;
  EndOfMonth = pgm_read_word(&MonthEnds[(EVENT_INACTIVE != pTime->LeapYear) ? 1 : 0][pTime->Month - 1]);
  return EndOfMonth + pTime->DayOfMonth;
}

This code might actually work, but if I were you, I’d test it pretty thoroughly before lashing it into your project…

 

The PROGMEM and pgm_read_word() stuff is the Arduino mechanism that puts the lookup table into Flash program memory rather than the ATMega168’s exceedingly limited RAM space. The definitive word about that process resides there.

Memo to Self: Using const simply makes the variable kinda-sorta read-only, but still initializes RAM from Flash. The PROGMEM routines delete the RAM copy entirely.

Advertisements

,

Leave a comment