I have been reprogramming a clock to count the days as well as the hours. (If you are curious, this is the Alpha Clock Five from Evil Mad Science.)
I have decided to limit my calendar to the year range 2000 to 2099. This is because the Chronodot which came with the clock uses only a simple leap-year rule.
Here are some calendar functions I came up with.
days2greg seems to work pretty well
greg2days has not really been tested has been tested a little, and some bugs have been fixed
days2wknum is untested has not been very well tested
Any comments?
byte bin2bcd (byte x) { // useful conversion function
return ((x/10)*16 + (x%10));
}
byte bcd2bin (byte x) { // another useful conversion function
return ((x/16)*10 + (x%16));
}
unsigned long days2greg (unsigned int x) {
// input to this function is a day number with day 0 = Friday 1 March 1996
// output is 0x0WDDMMYY (ISO weekday number, then BCD date, BCD month, BCD year)
// not lexically sortable but who cares, not for calculations anyway
if ((x<MinValidDays) || (x>MaxValidDays)) return 0xDEADBEEF;
byte w=5;
w+=(x%7);
if (w>7) w-=7; // this gives us our weekday
byte y=0-4;
byte m=3;
byte d=1;
y+=(4*(x/1461)); // 4-year intervals
x%=1461;
// we can't use a modulo operation for 0 to 3 years
if (x>=730) {y+=2; x-=730;} // 2 years
if (x>=365) {y++; x-=365;} // 1 year
if (x>=306) {y++; m=1; x-=306;} // 10 months: advance to January
else if (x>=153) {m=8; x-=153;} // 5 months: advance to August
if (x>=122) {m+=4; x-=122;} // 4 months
if (x>=61) {m+=2; x-=61;} // 2 months
if (x>=31) {m++; x-=31;} // 1 month
// now we have the right year and month
d+=x; // the day of the month
// next, calculate output
unsigned long r = w;
r = (r<<8) + bin2bcd(d);
r = (r<<8) + bin2bcd(m);
r = (r<<8) + bin2bcd(y);
return r;
}
unsigned int greg2days (unsigned long x) { // inverse of days2greg
byte y; byte m; byte d; byte w;
unsigned int q; unsigned int r; unsigned int s;
y = x & 255; x = x >> 8;
m = x & 255; x = x >> 8;
d = x & 255; x = x >> 8;
w = x & 255;
if (((y&15)>9) || ((m&15)>9) || ((d&15)>9)) return 50000;
if ((w==0) || (w>7)) return 50000;
y = bcd2bin(y);
m = bcd2bin(m);
d = bcd2bin(d);
if ((y>99) || (m<1) || (m>12) || (d==0)) return 50000;
switch (m) {
case 4:
case 11:
case 9:
case 6:
if (d>30) return 50000;
break;
case 2:
if (((y%4)>0) && (d>28)) return 50000;
else if (d>29) return 50000;
break;
default:
if (d>31) return 50000;
}
// 50000 = an error code
// (year, month, day, and/or weekday is invalid or out of range)
// next we do some math
q = 45 + (y*12) + m; // count of months with month 0 = March 1996
r = 1461*(q/48); q%=48; // 4-year intervals
r += 365*(q/12); q%=12; // years
r += 153*(q/5); q%=5; // 5-month intervals
r += 61*(q/2); q%=2; // 2-month intervals
if (q) {r+=31; q--;} // one month
r += (d-1); // days within the month
// now r is a count of days with day 0 = Friday 1 March 1996
s = 5 + (r%7); if (s>7) s-=7; // s is our expected day of the week
if (s!=w) return 50001; // error code for unexpected day of the week
return r;
}
byte days2wknum (unsigned int x){
// input to this function is a day number with day 0 = Friday 1 March 1996
// output is week number in BCD format
// ISO week numbering is used
if ((x<MinValidDays) || (x>MaxValidDays)) return 0xEE;
x -= 306; // move start date to 1997-01-01 (Wed)
unsigned int z = (x+2) % 7; // days since most recent Monday (zero-based)
x = x - z + 3; // round to nearest Thursday
x %= 1461; // get rid of 4-year intervals
if (x >= 730) x -= 730; // 2 years
if (x >= 365) x -= 365; // 1 year
// now x is (zero-based) days since start of year
byte w = (x / 7) + 1; // ISO week number
return bin2bcd(w);
}