Anyone have a sketch to calculate the day of week based on the date?

I can't use the Time Library in my project. To do so would require an entire rewrite, and it'll mess bunch of stuff up. I just need a simple function that can calculate the day of week based on a given date.

Does anyone have a code snippet that can do that?

There is a function in the Time library to do that. Even without using the library, you could borrow the function.

//
//    FILE: dayOfWeek.ino
//  AUTHOR: Rob Tillaart
// VERSION: 2013-09-01
// PURPOSE: experimental day of week code (hardly tested)
//     URL:
//
// Released to the public domain
//

void setup()
{
  Serial.begin(9600);

  for (int d =1; d<30; d++)
    Serial.println(dayOfWeek(2013,9,d));
}

void loop()
{
}

#define LEAP_YEAR(Y)     ( (Y>0) && !(Y%4) && ( (Y%100) || !(Y%400) ))     // from time-lib

int dayOfWeek(uint16_t year, uint8_t month, uint8_t day)
{
  uint16_t months[] = 
  {
    0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 // days until 1st of month
  };   

  uint32_t days = year * 365;   // days until year 
  for (uint16_t i = 4; i < year; i+=4) 
  {
    if (LEAP_YEAR(i)) days++;  // adjust leap years
  }

  days += months[month-1] + day;
  if ((month > 2) && LEAP_YEAR(year)) days++;
  return days % 7;
}

Just a few days ago I wrote one.

I posted it here:
http://forum.arduino.cc/index.php?topic=197253.msg1456592#msg1456592
Mine not only computes the day of the week, it also checks for bad dates.

robtillaart's function has a longer date range, but will involve thousands of modulus operations (these are slow) for any reasonable choice of date. He also did not specify which number stands for which day of the week.

+1 on using the macro that comes with Time library.

the macro is defined as (and way simpler than the two examples above)

#define dayOfWeek(_time_)  ((( _time_ / SECS_PER_DAY + 4)  % DAYS_PER_WEEK)+1) // 1 = Sunday

just call
dayOfWeek(now()) to get the day of week for the current day.

why reinvent the wheel?

doughboy:
just call
dayOfWeek(now()) to get the day of week for the current day.

why reinvent the wheel?

He said he can't use the Time library.

Besides, did you actually read that macro?
How does one divide a calendar date by anything? Clearly, the thing is meant to operate on a count of seconds rather than on
a calendar date.

ok, I missed the part where OP said he cannot use time library.

I'd be more curious as to why and address that.

Just the same, the time library code is better, as Paul said, OP can adapt that. Just takes a little creativity. :stuck_out_tongue:

At first I could not use Time library because I need to call time function now() from that ISR, so I modified the Time library with a few lines change so I have an interrupt friendly now() function. No need to reinvent the wheel.

doughboy:
Just the same, the time library code is better,

if you don't mind doing an awful lot of arithmetic.

Mine doesn't have division, or even multiplication for that matter.

@Odometer,
Three remarks on your interesting code

First the return value of 0 for invalid days is a good added value. I would have chosen 255 or -1 for error prevent interference with the ISO 8601 standard DOW numbering that defines the 0 AND 7 for Sunday.

Second, I would give the interface for year an int (uint16_t) so I can put in the real year. You still can support only 2000-2099 by means of one extra subtraction +test, and declare the other years "invalid". Would be more user friendly imho.

Checked the footprint of your code size against mine (which I derived from time lib)
yours=2454
mine=2674
so your code clearly wins for size AND also for speed. The price of the support for extra centuries is quite high.
(I feel challenged :wink:

@odometer
tweaked your code to have a smaller footprint by combining double code, and use a nested switch.
The size of this variation == 2406

void setup() 
{
  Serial.begin(115200);
  Serial.println("Day of Week ");

  Serial.println(calcDayOfWeekU(13,11,11));
}

void loop() 
{
}
byte calcDayOfWeek(byte y, byte m, byte d) 
{
  if (y > 99 || d > 31 || d == 0) return 0; 

  byte w = 6 + (y + (y >> 2));  // w is 6++

  // correction for Jan. and Feb. of leap year
  if (((y & 3) == 0) && (m <= 2)) w--;  // w=5++

  w += d;  // w = 6++

  // using subtraction iso addition makes the while at end possible 1 iteration faster.
  switch (m) 
  {
  case 1: 
    w -= 6; 
    break;
  case 2:  
    if (d > ((y & 3) ? 28 : 29)) return 0; 
    w -= 3; 
    break;
  case 3:
    w -= 3; 
    break;
  case 5:  
    w -= 5; 
    break;
  case 7:
    break;
  case 8:  
    w -= 4; 
    break;
  case 10:
    w -= 6; 
    break;
  case 12:
    w -= 1; 
    break;
  default:
    if (d > 30) return 0; 
    switch (m) 
    {
    case 4:  
      break;
    case 6:  
      w -= 2; 
      break;
    case 9:  
      w -= 1; 
      break;
    case 11:  
      w -= 3; 
      break;
    default:
      return 0;
    }
  }

  // there are only 7 days in a week, so we "cast out" sevens
  while (w > 7) w = (w >> 3) + (w & 7);
  return w;
}

Thanks for the challenge :wink:

@robtillaart:
It looks like, in your version of my function, w can get as low as 0, which makes the "casting out sevens" return 0 in that case. Not good.
Here is my corrected version:

void setup() 
{
  Serial.begin(115200);
  Serial.println("Day of Week ");

  Serial.println(calcDayOfWeekU(13,11,11));
}

void loop() 
{
}
byte calcDayOfWeek(byte y, byte m, byte d) 
{
  if (y > 99 || d > 31 || d == 0) return 0; 

  byte w = 6 + (y + (y >> 2));  // w is 6++

  // correction for Jan. and Feb. of leap year
  if (((y & 3) == 0) && (m <= 2)) w--;  // w=5++

  w += d;  // w = 6++

  // using subtraction iso addition makes the while at end possible 1 iteration faster.
  switch (m) 
  {
  case 1: 
    w++; 
    break;
  case 2:  
    if (d > ((y & 3) ? 28 : 29)) return 0; 
    w -= 3; 
    break;
  case 3:
    w -= 3; 
    break;
  case 5:  
    w -= 5; 
    break;
  case 7:
    break;
  case 8:  
    w -= 4; 
    break;
  case 10:
    w++; 
    break;
  case 12:
    w -= 1; 
    break;
  default:
    if (d > 30) return 0; 
    switch (m) 
    {
    case 4:  
      break;
    case 6:  
      w -= 2; 
      break;
    case 9:  
      w -= 1; 
      break;
    case 11:  
      w -= 3; 
      break;
    default:
      return 0;
    }
  }

  // there are only 7 days in a week, so we "cast out" sevens
  while (w > 7) w = (w >> 3) + (w & 7);
  return w;
}

Here is my version of your function:

//
//    FILE: dayOfWeek.ino
//  AUTHOR: Rob Tillaart (with changes by odometer)
// VERSION: 2013-11-10
// PURPOSE: experimental day of week code (hardly tested)
//     URL:
//
// Released to the public domain
//

void setup()
{
  Serial.begin(9600);

  int y = 2013;
  for (int m = 9; m <= 12; m++) {
    for (int d = 1; d <= 31; d++) {
    {
      Serial.print(y,DEC);
      Serial.print("-");
      Serial.print(m,DEC);
      Serial.print("-");
      Serial.print(d,DEC);
      Serial.print(" ");
      Serial.println(dayOfWeek(y,m,d));
    }
    Serial.println("");
  }
}

void loop()
{
}

int dayOfWeek(uint16_t year, uint8_t month, uint8_t day)
{
  uint16_t months[] = 
  {
    0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 // days until 1st of month
  };
  
  // sanity checks for input
  if (year > 65000) return 0; // to prevent rollover of intermediate variables
  if ((month < 1) || (month > 12)) return 0;
  if ((month == 2) && (day == 29)) { // special case leap day
    if ((year % 4) > 0) return 0;
    if (((year % 100) == 0) && ((year % 400) > 0)) return 0;
  }
  else {
    if (days == 0) return 0;
    if (days > (months[month] - months[month-1])) return 0;
  }

  uint32_t days = year * 365;   // days before given year (ignoring leap days)

  uint16_t febs = year;
  if (m > 2) febs++; // number of completed Februaries
  
  // add in the leap days
  days += ((febs + 3) / 4);
  days -= ((febs + 99) / 100);
  days += ((febs + 399) / 400);

  days += months[month-1] + day;
  // now we have day number such that 0000-01-01(Sat) is day 1
  
  return ((days + 4) % 7) + 1; // for Mon = 1 ... Sun = 7
  
  // return ((days + 5) % 7) + 1; // for Sun = 1 ... Sat = 7
}

It looks like, in your version of my function, w can get as low as 0, which makes the "casting out sevens" return 0 in that case. Not good. Here is my corrected version:

You're right, w can indeed become 0, thanx for fixing!

It is clear that the price for the support for the extra years is quite high at least the way my version works.
I expect a more efficient version that support more than current century can be written.

How about this?

byte calcDayOfWeek(unsigned int y, byte m, byte d) 
{
  // cast out multiples of 400 from y
  y -= (2000 * (y >> 11));
  while (y >= 400) y -= 400;

  boolean leap = ((y & 3) == 0);
  if ((y == 100) || (y == 200) || (y == 300)) leap = false;

  if (d > 31 || d == 0) return 0; 

  byte w = 6;
  while (y >= 100) {y -= 100; w -= 2; }

  w += (y + (y >> 2));

  // correction for Jan. and Feb. of leap year
  if (leap && (m <= 2)) w--;

  // using subtraction iso addition makes the while at end possible 1 iteration faster.
  switch (m) 
  {
  case 1: 
    w++; 
    break;
  case 2:  
    if (d > (leap ? 29 : 28)) return 0; 
    w += 4; 
    break;
  case 3:
    w += 4; 
    break;
  case 5:  
    w += 2; 
    break;
  case 7:
    break;
  case 8:  
    w += 3; 
    break;
  case 10:
    w++; 
    break;
  case 12:
    w += 6; 
    break;
  default:
    if (d > 30) return 0; 
    switch (m) 
    {
    case 4:  
      break;
    case 6:  
      w += 5; 
      break;
    case 9:  
      w += 6; 
      break;
    case 11:  
      w += 4; 
      break;
    default:
      return 0;
    }
  }
  
  w += d;

  // there are only 7 days in a week, so we "cast out" sevens
  while (w > 7) w = (w >> 3) + (w & 7);
  return w;
}

It looks good!, I knew you could do it :slight_smile:

I'm busy for a project right now, maybe I'll give it a try later this evening when I have a spare duino.

this test should be moved up, maybe even add || (m==0) to it to "fail as fast as possible".
if (d > 31 || d == 0) return 0;

Hey, thanks guys. This worked a treat.

Hello odometer & robtillaart

I used your code, and it works great, but....I really would like to understand it.
So I started placing comments, but I still do not get it completely, maybe you can help me out here?

Specifically statements like: y -= ( 2000 * (y >> 11) );

or : boolean leap = ((y & 3) == 0);

or: w += (y + (y >> 2));

and: if (d > (leap ? 29 : 28)) return 0;

I assumed I could call the routine with YY, MM, DD (two digits max per variable), it does work, so that must be right.
BTW, I reversed the order of the three variables to DD, MM, YY, made more sense in the rest of my application.

Thanks and regards,

Satbeginner

//================================================================================
// Begin calcdayofweek( D, M, Y)
//================================================================================

byte calcDayOfWeek( unsigned int d, byte m, byte y )                // Call routine using DD, MM, YY, it returns the dayoftheweek, where Sun=1, Mon=2, Tue=3, Wed=4, Thu=5, Fri=6, Sat=7
{
  y -= ( 2000 * (y >> 11) );                                        // y = y - (2000 * (y >> 11) ) ?? convert '16' into '2016' ?? How??
  
  while (y >= 400) { y -= 400; }                                    // while (y >= 400) { y = y - 400; }cast out multiples of 400 from the entered year (step down in steps of 400?)                                       
  
  boolean leap = ((y & 3) == 0);                                    // do we have a leap-year?? if so, leap = 'true' (year can be divided by 4) 
  
  if ((y == 100) || (y == 200) || (y == 300)) leap = false;         // check if the year is a 'century', they are NO leapyears, but any multiple of '400' IS a leapyear
  if (d > 31 || d == 0) return 0;                                   // check for false dates, if so, exit in error, return '0'

  byte w = 6;                                                       // temp weekday variable used to determine the weekday, starts at '6' because of .....????
  while (y >= 100) {y -= 100; w -= 2; }                             // while (y >= 100) {y = y - 100; w = w - 2; } step down the year, and step down weekday too. Weekday -2 for every 100 year?

  w += (y + (y >> 2));                                              // w = w + (y + (y >> 2));

  // correction for Jan. and Feb. of leap year
  if (leap && (m <= 2)) { w--; }                                    // if ( leap = 'true' && (m <= 2) ) { w = w - 1; }

                                                                    // using substraction iso addition makes the while at end possible 1 iteration faster ???
  switch (m)                                                        // find weekday in the 12 month's
        {
        case 1:                                                     // if January 
        w++;                                                        // weekday = weekday +1 ;
        break;
        
        case 2:                                                     // if February
        if (d > (leap ? 29 : 28)) return 0;                         // ???? if leap AND date = 29 or 28, return 0; so exit in error, return '0' 
        w += 4;                                                     // weekday = weekday + 4;
        break;
  
        case 3:                                                     // if March
        w += 4;                                                     // weekday = weekday + 4;
        break;
  
        case 5:                                                     // if May
        w += 2;                                                     // weekday = weekday +2;
        break;
  
        case 7:                                                     // if July
        break;                                                      // do nothing
  
        case 8:                                                     // if August
        w += 3;                                                     // weekday = weekday + 3;
        break;
 
        case 10:                                                    // if October
        w++;                                                        // weekday = weekday + 1;
        break;
  
        case 12:                                                    // if December
        w += 6;                                                     // weekday = weekday + 6;
        break;
  
        default:                                                    // here when April, June, September, November
        if (d > 30) return 0;                                       // if date bigger than 30: impossible in these months, so exit with error, return '0'
        switch (m)                                                  // process these other months
              {
              case 4:                                               // if April
              break;                                                // do nothing
    
              case 6:                                               // if June
              w += 5;                                               // weekday = weekday + 5;
              break;
    
              case 9:                                               // if September
              w += 6;                                               // weekday = weekday + 6;
              break;
    
              case 11:                                              // if November
              w += 4;                                               // weekday = weekday + 4;
              break;
    
              default:                                              // no month's left to process, an answer must have been found before this point
              return 0;                                             // so exit in error, return '0'
              }
        }
 
  w += d;                                                           // weekday = weekday + date;

                                                                    // there are only 7 days in a week, so we "cast out" sevens
  while (w > 7) { w = ( w >> 3 ) + ( w & 7 ); }                     // while (w > 7) { w = (w >> 3) + (w & 7); }
  return w;                                                         // end of routine, return the weekday, where Sun=1, Mon=2, Tue=3, Wed=4, Thu=5, Fri=6, Sat=7
}
//================================================================================
// End calcdayofweek( D, M, Y)
//================================================================================

What is your date input for your current setup? Are you using an RTC module?

The thing giving you the date may already have info on day of week, but we have no info on your setup.

Hi,

thanks for your reply!

My D,M,Y come from a GPS receiver (NEO-6), which I modified to use the available 1PPS-signal (one Pulse per Second) to build a very accurate frequency generator.
The Neo is quite sensitive, even indoors, so I am pretty sure there will not be any invalid dates send to the routine.

Like this: http://www.arrl.org/files/file/QEX_Next_Issue/2015/Jul-Aug_2015/Marcus.pdf

Meanwhile I did some 'googleling' and found this page:

where they have a real lot information on the DayOfWeek topic.

From there I got this bit of (very compact!) code that does work too:

//================================================================================
// Begin calcdayofweek( D, M, Y)
//================================================================================
byte   calcDayOfWeek(int d, int m, int y)                                    // 1 <= m <= 12,  y > 1752 (in the U.K.)
    {                                                                        // https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week
        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;                   // Sun=0, Mon=1, Tue=2, Wed=3, Thu=4, Fri=5, Sat=6
    }
//================================================================================
// End calcdayofweek( D, M, Y)
//================================================================================

By supplying known dates to the original code by odometer & robtillaart, I figured out that the first bit of code strips the high years, so 3116 will become 1116, later the years will be reduced by subtracting 400, etc.

So I now know what it does, but still do not understand the syntax of these code-lines.
(The shifting part, etc)

The 'magic' number '6' -I think- is a known day on 1-1-0001, so from there on the shift in days is calculated?

This new code I found on Wikipedia has a similar but different approach, and seems to work too.

Attached you find my complete code for both the GPS-disciplined generator, single-wire rotate menu encoder and GPS clock.
Here more on the 'single wire rotary encoder' : Rotary Encoder made easy (Single Wire!) and debounced at the same time - Project Guidance - Arduino Forum

Un saludo,

Satbeginner

Si5351_vfo_LK_V20.ino (78.9 KB)

odometer:
How about this?

The code posted in #13 calculates wrong results in several cases.

Give it a try with this test code:

void setup()
{
 Serial.begin(9600);
 Serial.println(calcDayOfWeek(1999,12,31));
 
 Serial.println(calcDayOfWeek(2000,1,1));
 
  
}

DayOfWeek is surely never 7, it has to be in the range 0 to 6 only!