How can I make a function which calculates the weekday of a specific date in C?

256 Views Asked by At

Fixed: I had to do it with a pointer to call the values(I also changed the formular but I´m sure the old one would also work. The problem was the missing pointer):

sAppointment Calendar[MAXAPPOINTMENTS];

int dayOfWeek(sDate *date){
    static int jMonth[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
    int d = date->Day;
    int m = date->Month;
    int y = date->Year;

    if(m < 3) y--;

    return(y + (y/4) - (y/100) + (y/400) + jMonth[m - 1] + d) % 7;
}

I am writing a C Program which has a datastructure in which the variables of a date can be saved but it does not work for the weekday. It appears an error: "expression must have integral type"

The function is build like this, including a formular to calculate the weekday:

void dayOfWeek(sDate date){
    if(date.Month > 3) date.Year = date.Year - 1;
    date.WeekDay = ((date.Day + floor (2.6 * ((date.Month + 9) % 12 + 1) - 0.2) + date.Year % 100 + floor (date.Year % 100 / 4) + floor (date.Year / 400) - 2 * floor (date.Year / 100) - 1) % 7 + 7) % 7 + 1;
}

The date datastructure contents int values which looks like this (german language):

typedef enum {So, Mo, Di, Mi, Do, Fr, Sa} eDayofTheWeek;

typedef struct{
    int Day;
    int Month;
    int Year;
    eDayofTheWeek WeekDay;
} sDate;

I tried to set most of the formular to a variable which first containts everything before the modulo operator

int w;
w = ((date.Day + floor (2.6 * ((date.Month + 9) % 12 + 1) - 0.2) + date.Year % 100 + floor (date.Year % 100 / 4) + floor (date.Year / 400) - 2 * floor (date.Year / 100) - 1);
date.Weekday =( w % 7 + 7) % 7 + 1;

This only led to the weekday value being 0. Other formulars also let the value of being 0.

4

There are 4 best solutions below

0
chux - Reinstate Monica On BEST ANSWER

OP's code has at least these problems:

No update for caller

void dayOfWeek(sDate date){ does not provide any result back to the caller ("This only led to the weekday value being 0"). Instead, pass by reference.

void dayOfWeek(sDate *date) {
//                   ^   

% not valid with floating point

Before applying %, convert the value to an int. Note: C does not has a modulo operator but a remainder operator.

Wrong test

// if(date.Month > 3)   date.Year = date.Year - 1;
if(date.Month < 3) { date.Year = date.Year - 1;  maybe adjust Month }

I was unable to fully fix OP's code.

Consider avoiding one-liner code and instead provide more clarity.

Below works for month and day outside the usual range.

#define MARCH 3
#define DaysPer400Years   (400*365LL + 97)
#define DaysPer100Years   (100*365LL + 24)
#define DaysPer4Years     (4*365LL + 1)
#define DaysPer1Year      365LL
#define DayNumber1970Jan1 719469
#define MonthsPerYear 12

long long DayNumber0(int year, int Month, int Day, long epoch) {
  long long dn = Day;
  long long y = year;
  y += Month / 12;
  Month %= 12;
  while (Month < MARCH) {
    Month += 12;
    y--;
  }
  // And then a miracle occurs. Scale by 30.59375 and add an offset.
  dn += ((Month - MARCH) * (7832 / 4) + (140 / 4)) >> (8 - 2);
  dn += (y / 400) * DaysPer400Years;
  y %= 400;
  dn += (y / 100) * DaysPer100Years;
  y %= 100;
  dn += (y / 4) * DaysPer4Years;
  y %= 4;
  dn += y * DaysPer1Year;
  return dn - epoch;
}

int WeekDay_chux(int year, int Month, int Day) {
  #define WEEK_JAN1AD1_OFFSET 5
  int dow = DayNumber0(year, Month, Day, WEEK_JAN1AD1_OFFSET) % 7;
  return (dow + 7 )%7;
}

Note that DaysPer400Years is a multiple of 7 (for the Gregorian Calendar). Thus we can take short cuts and start with year %= 400 to work with smaller numbers.

3
Jabberwocky On

You probably did something wrong in some code you didn't show.

This works fine for me:

#include <stdio.h>
#include <math.h>

typedef enum { So, Mo, Di, Mi, Do, Fr, Sa } eDayofTheWeek;

int main()
{
  int Day = 5;
  int Month = 12;
  int Year = 2023;
  int w = (Day + floor(2.6 * ((Month + 9) % 12 + 1) - 0.2) + Year % 100 + floor(Year % 100 / 4) + floor(Year / 400) - 2 * floor(Year / 100) - 1);
  
  int Weekday = (w % 7 + 7) % 7 + 1;
  printf("Weekday = %d\n", Weekday);

  eDayofTheWeek WeekDay = Weekday;
  // add more test code here.
  // maybe the problem is in your test code you didn't show
}

Output I get:

Weekday = 2

Which looks correct to me, 2 being Tuesday. But don't ask me why and how this formula works.

12
greg spears On

Finding the day of week, as others have noted, is not a trivial undertaking. Ordinarily I like to modify the poster's code and answer with that, but there is a lot going with this calculation: it is complex.

Here is a solution for discovering the weekday developed sometime back. It has abundant comments which hopefully explain everything you need to know: why it works, how it works, etc. It does not resort to std c math library - not needing floor() et al.

Runnable code to show it works is available here.

Hope it helps.

EDIT: Updated code. Slightly more robust, shorter, and more clarification and details in the comments.

#include <stdio.h>

// Boolean Macro for determining if year argument is a leap year.
// Principle: Each year evenly divisible by 4 is a leap year with the
//            exception of centennials - only centennials evenly divisible
//            by 400 are leap years.
#define IS_LEAP_YEAR(yr) (!((yr)%400) || (!((yr)%4) && (((yr)%100)))  )

char *ascii_days[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};

/*------------------------------------------------------------------------------
 weekday()
 
 Purpose: Given a year, month, and date, return a value (0-6) for
           which day of the week, where 0 = Sunday. 

 Params: year, month, and date, long integers
 
 Notes: uses IS_LEAP_YEAR() macro

 Caveat: The Gregorian Calendar reformed the concept of leap years in Oct 1582 
        to our current leap year concept.  Hence, this code may not produce
        accurate weekdays for dates prior to 1583.  However, it is Proleptic 
        Gregorian Calendar compliant for dates all the way back to 1 AD.  This
        is somewhat of a deep rabbit hole; lots of info is available online.
*------------------------------------------------------------------------------*/
int weekday(long year, long month, long date)
{
    long days, wkday, num_lp_yrs;
    int mon;
    int month_days[12]={31,28,31,30,31,30,31,31,30,31,30,31};

    /* Notice we say, "year-1" because this parameter year is not complete 
    * and hence not counted.  We will count the days of the current year later. */
    num_lp_yrs = (year-1)/4;  /* How many leap years? One every 4 yrs, but...*/
    num_lp_yrs -= (year-1)/100; /*...centennials are not leap years...*/
    num_lp_yrs += (year-1)/400;   /*...unless that centennial is a multiple of 400*/
    days = (year-1)*365 + num_lp_yrs; /*Capture total number of days*/

    /* If this is a leap year make February 29 days */
    if(IS_LEAP_YEAR(year))
        month_days[1] = 29; 

    /* Now, let's get the days spent of the parameter/'current' (incomplete) year 
    * by examining month and day of the year....because our days count (so far) is
    * only up to January 1st of the parameter year.   Hence, we must add the 
    * days of the current year (since Jan 1st) to the days variable.  Parse
    * and sum every applicable month of the array... */
    for(mon = 0; mon < month-1 && mon < 12; mon++)
        days += month_days[mon];

    /* Finally, add in the parameter date given. */
    days += date;

    /* 7 days in a week, so... modulo 7 nicely tells the weekday*/
    wkday = days%7;

    return (int)wkday;
}


int main() 
{
    int day_of_week = weekday(2023, 12, 5);
    char *sDay = ascii_days[day_of_week];

    printf("Weekday is: %s\n", sDay);

    return 0;
}

Output:

Weekday is: Tue
0
chux - Reinstate Monica On

A test harness should you want to test your code

#include <stdio.h>
#include <time.h>

int mktime_dow(int year, int month, int day) {
  struct tm t = {0};
  t.tm_year = year - 1900;
  t.tm_mon = month - 1;
  t.tm_mday = day;
  if (mktime(&t) == -1) {
    return -1;
  }
  return t.tm_wday;
}

int main(void) {
  for (int year = 1600; year <= 2400; year++) {
    for (int mon = 1; mon <= 12; mon++) {
      for (int day = 1; day <= 2; day++) {  // Extend to 28 - 31 if desired.
        int w0 = mktime_dow(year, mon, day);
        int w1 = function under text(year, mon, day);
        if (w0 != w1) {
          printf("%d/%02d/%02d %d != %d\n", year, mon, day, w0, w1);
          exit(1);
        }
      }
    }
  }
  puts("Done");
  return 0;
}