Go Down

Topic: Anyone have a sketch to calculate the day of week based on the date? (Read 7276 times) previous topic - next topic

ryemac3

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?

PaulS

There is a function in the Time library to do that. Even without using the library, you could borrow the function.
The art of getting good answers lies in asking good questions.

robtillaart

Code: [Select]
//
//    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;
}
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

odometer

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.

doughboy

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

the macro is defined as (and way simpler than the two examples above)
Code: [Select]

#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?

odometer


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.

doughboy

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. :P

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.

odometer


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.

robtillaart

@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 ;)
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

robtillaart

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

Code: [Select]

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 ;)
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

odometer

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

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;
}

odometer

Here is my version of your function:
Code: [Select]

//
//    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
}

robtillaart

Quote
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.
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

odometer

How about this?
Code: [Select]

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;
}

robtillaart

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

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;
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

Go Up