/*---------------------------------------------------------------------- File : dow.c Contents: compute the day of the week for a given date Author : Christian Borgelt History : 07.11.1997 file created 08.11.1997 comments on vectors added ----------------------------------------------------------------------*/ #include #include /*---------------------------------------------------------------------- Preprocessor Definitions ----------------------------------------------------------------------*/ #define USESWITCH 1 /* set to 0 to get the vector version */ /*---------------------------------------------------------------------- Constants ----------------------------------------------------------------------*/ #if !USESWITCH /* if to use vector constants */ const int offsets[12] = { /* offsets to first day of each month */ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; const char *names[7] = { /* names of the days of the week */ "Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" }; #endif /*---------------------------------------------------------------------- Besides the switch statement this program also demonstrates how to define, initialize and use vectors. (The conditional compilation used above --- #if !USESWITCH ... #endif --- is explained below.) As already indicated in the file `heron.c' a vector is an ordered set of objects of the same type. Each element of a vector is called a field. The number of fields in a vector can vary. You can have vectors with 2 fields, 100 fields or even 1000000 fields. How many fields there are in a vector is usually fixed when the vector is defined. You define a vector by appending to the variable name a '[' (an opening bracket), then a number, and then a ']' (a closing bracket). The brackets indicate that the variable is meant to be a vector. The number between the brackets indicates how many fields the vector shall have. In the above definition the first vector has 12 fields, each of which is of type 'int', the second has 7 fields, each of which is of type 'char *' (which is simply a string). In addition the above definition tells us that the vectors are 'const' --- constant, that is, unchangeable. You cannot change the values of the fields of these vectors. If you need a vector that can be modified, just drop the 'const'. You can access the elements of a vector using indices. An index is simply the number of a field in the vector. In C the range of indices for a vector is fixed as soon as the size of the vector is fixed. The index of the first field is always 0. That is, by writing names[0] you retrieve the contents of the first field of the 'names' vector. This is the string "Saturday". The last index is the size of the vector -1. That is, by writing names[6] you retrieve the contents of the last field of the 'names' vector. This is the string "Friday". Note the -1 (size of the vector -1). Never try to access names[7] or names[8] or any field like that. The compiler will not complain, but this tries to access a field in the vector that is not defined. The initialization of a vector as shown above --- just enclose the values of the fields of the vector in curly braces --- is possible only when you define the vector. (At least according to the ANSI standard. The Gnu C-compiler allows such assignments in normal code, if you do not specify the '-ansi' option. Do not exploit this possibility. It makes your program less portable.) With the vectors defined above we can solve the day of the week problem much more elegantly than with a switch statement. ----------------------------------------------------------------------*/ /*---------------------------------------------------------------------- Functions ----------------------------------------------------------------------*/ int main (int argc, char *argv[]) { /* --- compute day of the week */ int y; /* year */ int m; /* month within the year */ int d; /* day within the month */ if (argc != 4) { /* if wrong number of arguments given */ printf("usage: %s dd mm yyyy\n", argv[0]); printf("compute the day of the week for a given date\n"); return 0; /* print a usage message */ } /* and abort the program */ d = atoi(argv[1]); /* get the day within the month, */ m = atoi(argv[2]); /* the month within the year and */ y = atoi(argv[3]); /* the year from the arguments */ if ((d < 1) || (d > 31) /* do a rough check on the arguments */ || (m < 1) || (m > 12) /* for plausibility (1583 was the */ || (y < 1583)) { /* year of the calendar reform) */ printf("%s: illegal date\n", argv[0]); return 1; } #if USESWITCH /* if to use the switch statement */ switch (m) { /* evaluate the month within the year */ case 1: break; /* For each month add the number of */ case 2: d += 31; break; /* days in all the months preceding */ case 3: d += 59; break; /* it in the year, i.e. add the */ case 4: d += 90; break; /* offset in days from the beginning */ case 5: d += 120; break; /* of the year to the first day of */ case 6: d += 151; break; /* the month. After this do a special */ case 7: d += 181; break; /* check for leap years (in which, */ case 8: d += 212; break; /* unnecessary to say, February has */ case 9: d += 243; break; /* an extra day: the intercalary day).*/ case 10: d += 273; break; /* We thus arrive at the day within */ case 11: d += 304; break; /* the year (stored in d). */ case 12: d += 334; break; } #else /* if to use the vector constants */ d += offsets[m-1]; /* add offset to 1st day of the month */ #endif /* #if USESWITCH */ if ( (y % 4 == 0) /* if the year is a leap-year */ && ((y % 100 != 0) || (y % 400 == 0)) && (m > 2)) /* and we are later than February, */ d += 1; /* add one for the intercalary day */ d += y +(y-1)/4 -(y-1)/100 +(y-1)/400; /* Add to the day within the year a value that takes into account */ /* the year (see below for an explanation). The remainder of the */ /* result divided by seven yields the day of the week for the date. */ #if USESWITCH /* if to use the switch statement */ switch (d % 7) { /* compute and print day of the week */ case 0: printf("Saturday\n"); break; case 1: printf("Sunday\n"); break; case 2: printf("Monday\n"); break; case 3: printf("Tuesday\n"); break; case 4: printf("Wednesday\n"); break; case 5: printf("Thursday\n"); break; case 6: printf("Friday\n"); break; } #else /* if to use the vector constants */ printf("%s\n", names[d % 7]); /* print the day of the week */ #endif /* #if USESWITCH */ return 0; /* return 'ok' */ } /* main() */ /*---------------------------------------------------------------------- Note that this program does only a rough check on the arguments for plausibility. It is still possible to enter an invalid date, for example '30 02 1997' or '31 04 1996', and the program will produce a result. Exercise: Modify this program (or your own) to provide a full check for a valid date. I.e. it should not be possible to enter days greater than the number of days in the month or a date before October 15 1583. Provide a brief explanation for the rejection. (Hint: This is very simple in the vector version, if you define an additional vector holding the days per month.) Historical remark: Initiated by Pope Gregory XIII (1502-1585) the Julian calendar, which had been in use up to that year, was replaced by the Gregorian calendar in 1583. This had become necessary, since the Julian calendar does not measure exactly enough the time it takes the earth to travel around the sun. A slight shift of the seasons w.r.t. the calendar had already become perceivable. To get everything straight again, the days between October 4 1583 and October 15 1583 were skipped and from that date on the Gregorian calendar was used, which provides a much more exact measurement. (Of course, in those times it was much more difficult than today to synchronize events all over Europe, leave alone the world, and thus some countries still used the Julian calendar several years later. We deliberately ignore such subtleties.) Where does the formula y +(y-1)/4 -(y-1)/100 +(y-1)/400 +d for the day of the week of day d in year y come from? Hypothetically assume that the Gregorian calendar has been in operation ever since (which it has not, see above), and that the day of the week of the 01.01.0001 has been fixed to be a Monday. If we then compute the number of days that elapsed since this date we can easily find the day of the week for a given date. If the number of days divided by seven leaves remainder 0, the day of the week must be Monday, if it leaves remainder 1, it must be Tuesday, and so on. How do we compute the number of days? For simplicity, let us first look at January 1 of a given year y. Then, since year 1, y-1 years have passed. How many days did they contain? Well, a normal year has 365 days, so we compute (y-1)*365. But we have to take care of leap-years. Since in the Gregorian calendar leap-years are those whose number is divisible by 4 and not by one hundred, or, if by one hundred, than also by four hundred, we can compute the number of extra days from these years as follows: We add the number of years from year 1 up to year y (but not including year y, since we look at January 1 in year y and are thus unaffected by an intercalary day in year y), whose number is divisible by 4. There are (y-1)/4 such years (fraction discarded), as you can easily verify. But by adding this number we counted intercalary days for the years whose number is divisible by 100, i.e. we added too many days. So we remove the number of years between year 1 and year y whose number is divisible by 100. There are (y-1)/100 such years (fraction discarded). But by subtracting this number we removed also the intercalary days of the years whose number is divisible by 400. So we have to readd the number of these years. There are (y-1)/400 of them (fraction discarded). We arrive at (y-1)*365 +(y-1)/4 -(y-1)/100 +(y-1)/400. Let us now take an arbitrary day d within a year. To compute its day of the week we have to add the number of days that passed since January 1 of the year, which are d-1. We thus arrive at the formula (y-1)*365 +(y-1)/4 -(y-1)/100 +(y-1)/400 +(d-1) for the number of days since the 01.01.0001, which is equivalent to (y-1) +(y-1)*364 +(y-1)/4 -(y-1)/100 +(y-1)/400 +d -1 = y +(y-1)*52*7 +(y-1)/4 -(y-1)/100 +(y-1)/400 +d -2. Since we only look at the remainder of this number divided by 7, we can add or remove arbitrary multiples of seven without changing the result. Now (y-1)*52*7 is obviously a multiple of seven, so we can remove it. This yields y +(y-1)/4 -(y-1)/100 +(y-1)/400 +d -2. We said that if this number divided by seven leaves remainder 0, the day of the week is Monday. But this is the same as saying that if this number plus 2 divided by seven leaves remainder 2, the day of the week is Monday. Thus, by remapping the days, we can get rid of the -2 at the end of the formula. In the new mapping remainder 0 indicates a Saturday, remainder 1 a Sunday, and so on. The switch statement as used above tests for one of several fixed cases. The expression after the switch, which must evaluate to an integer number, is tested and then the code following the corresponding case label is executed. The 'break' keyword is used to terminate the code for one case. When a 'break' is encountered within a switch statement, the program is continued after the closing curly brace of the switch. This is very important. Only a 'break' forces the program to leave the switch statement (or a `return', which terminates the function the switch is in). If there is no 'break' before the next case label, the code for that case is executed, too. This may be a little confusing at first, but it can be a very nice feature in certain circumstances. So do not forget the 'break', if you want the program to continue after the end of the switch. A more thorough treatment of how the switch statement and case labels work is provided in the file 'duff.tex', which discusses a piece of code known as 'Duff's device'. In addition to the switch statement and the use of vectors this program demonstrates how conditional compilation works. This program is really two programs, one of which solves the day of the week problem using switch statements, whereas the other uses constant vectors. (It is easy to see that the vector version is much more elegant, but this is not an issue here.) Depending on the value of the preprocessor constant USESWITCH either the version that uses switch statements (if USESWITCH is 1 = true) or the vector version (if USESWITCH is 0 = false) is compiled. Conditional compilation is something that is provided entirely by the preprocessor. It tests the condition after the #if and according to the result removes either the code following the #if or the code following the #else (which can also be missing, see the definitions of the constant vectors). Conditional compilation comes in handy, if your compiler cannot deal with nested comments (like the gcc). Then you can still `comment out' a part of your program by using #if 0 ... #endif around that part. In addition, #if-directives can always be nested. ----------------------------------------------------------------------*/