xref: /csrg-svn/usr.bin/cal/README (revision 39233)
1*39233SbosticProgram Design
2*39233Sbostic
3*39233SbosticThis program exactly duplicates the operation of the original unix "cal"
4*39233Sbosticprogram.  It was designed with that intent, so no "improvements" were made
5*39233Sbosticto either the command line syntax or to the error reporting.  The main
6*39233Sbosticgoal was to allow replacement of the existing binary with a freely
7*39233Sbosticredistibutable version without breaking any existing applications that
8*39233Sbosticmight be built on top of the original.
9*39233Sbostic
10*39233SbosticThe date routines were written from scratch, basically from first
11*39233Sbosticprinciples.  The algorithm for calculating the day of week from any
12*39233Sbosticgregorian date was "reverse engineered". This was necessary as most of
13*39233Sbosticthe documented algorithms have to do with date calculations for other
14*39233Sbosticcalendars (e.g. julian) and are only accurate when converted to gregorian
15*39233Sbosticwithin a narrow range of dates.
16*39233Sbostic
17*39233SbosticI take 1 jan 1 to be a Saturday because that's what cal says and I couldn't
18*39233Sbosticchange that even if I was dumb enough to try. From this we can easily
19*39233Sbosticcalculate the day of week for any date. The algorithm for a zero based
20*39233Sbosticday of week:
21*39233Sbostic
22*39233Sbostic	calculate the number of days in all prior years (year-1)*365
23*39233Sbostic	add the number of leap years (days?) since year 1
24*39233Sbostic		(not including this year as that is covered later)
25*39233Sbostic	add the day number within the year
26*39233Sbostic		this compensates for the non-inclusive leap year
27*39233Sbostic		calculation
28*39233Sbostic	if the day in question occurs before the gregorian reformation
29*39233Sbostic		(3 sep 1752 for our purposes), then simply return
30*39233Sbostic		(value so far - 1 + SATURDAY's value of 6) modulo 7.
31*39233Sbostic	if the day in question occurs during the reformation (3 sep 1752
32*39233Sbostic		to 13 sep 1752 inclusive) return THURSDAY. This is my
33*39233Sbostic		idea of what happened then. It does not matter much as
34*39233Sbostic		this program never tries to find day of week for any day
35*39233Sbostic		that is not the first of a month.
36*39233Sbostic	otherwise, after the reformation, use the same formula as the
37*39233Sbostic		days before with the additional step of subtracting the
38*39233Sbostic		number of days (11) that were adjusted out of the calendar
39*39233Sbostic		just before taking the modulo.
40*39233Sbostic
41*39233SbosticIt must be noted that the number of leap years calculation is sensitive
42*39233Sbosticto the date for which the leap year is being calculated. A year that occurs
43*39233Sbosticbefore the reformation is determined to be a leap year if its modulo of
44*39233Sbostic4 equals zero. But after the reformation, a year is only a leap year if
45*39233Sbosticits modulo of 4 equals zero and its modulo of 100 does not. Of course,
46*39233Sbosticthere is an exception for these century years. If the modulo of 400 equals
47*39233Sbosticzero, then the year is a leap year anyway. This is, in fact, what the
48*39233Sbosticgregorian reformation was all about (a bit of error in the old algorithm
49*39233Sbosticthat caused the calendar to be inaccurate.)
50*39233Sbostic
51*39233SbosticOnce we have the day in year for the first of the month in question, the
52*39233Sbosticrest is trivial. Running diff on any output of this program and the
53*39233Sbosticequivalent output from the original cal reports no difference.  This was
54*39233Sbosticconfirmed by a script that ran them for all possible inputs (and took
55*39233Sbosticapproximately 36 hours to complete on a sun-3.)
56