[net.lang.c] Calendar Functions

leeke@cascade.STANFORD.EDU (Steven D. Leeke) (09/02/86)

Could someone please give me some pointers to functions for creating
calendars? e.g. for any day from 1900 to as far in the future as possible.
Actual code would be GREATLY appreciated - C preferred.

Thanks,


-- 
Steven D. Leeke, Center for Integrated Systems, Stanford University
    {ucbvax,decvax}!decwrl!glacier!leeke, leeke@cascade.stanford.edu

Disclaimer: I disclaim any knowledge of the above message and its contents.

dml@loral.UUCP (Dave Lewis) (09/10/86)

In article <206@cascade.STANFORD.EDU> leeke@cascade.UUCP (Steven D. Leeke)
writes:
>Could someone please give me some pointers to functions for creating
>calendars? e.g. for any day from 1900 to as far in the future as possible.
>Actual code would be GREATLY appreciated - C preferred.
>

  Here's a collection of calendar functions; writing a main() driver
function should be trivial. These were originally assignments for a Pascal
class (what the hey, an easy 4 credits with an A+) and I rewrote them in
C for the hell of it. Good for contrasting Pascal and C - things I had to
work around in Pascal came out as straight code in C.

/*  Determines whether a given year is a leap year
 */

leapyear (year)
int year;
{
  if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))
     return (1);
  else
     return (0);
}

/*  Returns #days in month given month and year, taking
 *    leap years into account for February.
 */

daysinmonth (month, year)
int month, year;
{
  if (month == 2)          /* Is it February?             */
     if (leapyear (year))  /* If so, is it a leap year?   */
	return (29);       /* 29 days in Feb in leap year */
     else
	return (28);       /* 28 days if not              */
  else{
     if (month > 7)        /* Is it August -> December?   */
	month++;           /* Invert even/odd state if so */
     if (month & 1)        /* Odd months have 31 days     */
	return (31);
     else
	return (30);       /* Even months have 30 days    */
  }
}

/*  Determines whether a given date is valid
 */

validdate (month, day, year)
int month, day, year;
{
  if (month < 1 || month > 12 || day < 1 ||
     day > daysinmonth (month, day, year) ||
     year < 1583 || year > 9999)
	return (0);
  else
	return (1);
}

/*  Given a valid date (month, day, and year) Zeller will
 *    return an integer representing the day of week that
 *    date will fall on. 0 = Sunday, 6 = Saturday.
 */

zeller (month, day, year)
int month, day, year;
{
  int century;
  month -= 2;       /* Years start on March 1 so adjust standard date */
  if (month < 1) {
     month += 12;
     year--;
  }
  century = year / 100;
  year = year % 100;
  return (((2.6 * month - 0.1) + day + year + year / 4
	   + century / 4 - century * 2) % 7);
}

  These functions will work for any date between March 1, 1583 and December 31,
9999. Have fun!

-------------------------------
		Dave Lewis    Loral Instrumentation   San Diego

  hp-sdd --\     ihnp4 --\
  sdcrdcf --\      bang --\   kontron -\
  csndvax ---\   calmasd -->-->!crash --\
  celerity --->------->!sdcsvax!sdcc3 --->--->!loral!dml  (uucp)
  dcdwest ---/                 gould9 --/

 When the government runs it, it's called a Lottery.
 When somebody else runs it, it's called a Numbers Racket.

-------------------------------

greg@ncr-sd.UUCP (Greg Noel) (09/12/86)

In article <1229@loral.UUCP> dml@loral.UUCP (Dave Lewis) writes:
>/*  Given a valid date (month, day, and year) Zeller will
> *    return an integer representing the day of week that
> *    date will fall on. 0 = Sunday, 6 = Saturday.
> */
>
>zeller (month, day, year)
>int month, day, year;
>{
>	[ various code deleted..... ]
>  return (((2.6 * month - 0.1) + day + year + year / 4
>	   + century / 4 - century * 2) % 7);
>}

Note that the expression above is in floating point.  This may produce bogus
results if the divisions (which are supposed to be truncated) end up being
done in floating point (they \shouldn't/ be, but....).  Certainly, converting
all those ints to floats to be added and converted back to an int will take
a lot of time.  Also, it is possible that the major expression (prior to the
modulo operation) can be negative, so it is best to add in a bias value that
is zero mod seven to be sure that the calculated modulus is also positive.
Thus, a logically equivalent version, done with all integer arithmetic is:

	return ((26 * month - 1)/10 + day + year + year/4
		+ century/4 - century*2 + 777) % 7;

BTW, the reason it's called a congruence is that with this formulation it's
easy to demonstrate that the the day-of-the-year pattern repeats every three
hundred years.
-- 
-- Greg Noel, NCR Rancho Bernardo    Greg@ncr-sd.UUCP or Greg@nosc.ARPA

devine@vianet.UUCP (Bob Devine) (09/12/86)

Dave Lewis writes:
> leapyear (year)
> int year;
> {
>   if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))
>      return (1);
>   else
>      return (0);
> }

  While this works, it is overkill.  Unless you believe that your
code will make it to the year 2100, a simple test for divisibility
by 4 is sufficient.  If you really want an algorithm for all years,
you then need to also test for years divisible by 4000...

int
leapyear(year)
    int year;
{
    /* works for the years 1901-2099 */
    return(year%4);
}

Or if you want a simple cpp macro:

#define  isleapyear(year)   ((year)%4)

Bob Devine

pdg@ihdev.UUCP (P. D. Guthrie) (09/15/86)

>Or if you want a simple cpp macro:
>
>#define  isleapyear(year)   ((year)%4)
>
>Bob Devine

Or if you want a complex cpp macro
to return the number of days in a year......

#define num_day(x) ((x)%4 ? (x)%100 ? (x)%400 ? 366 : 355 : 356 : 355)

franka@mmintl.UUCP (09/15/86)

In article <1229@loral.UUCP> dml@loral.UUCP (Dave Lewis) writes:
>daysinmonth (month, year)
>int month, year;
>{
>  if (month == 2)          /* Is it February?             */
>     if (leapyear (year))  /* If so, is it a leap year?   */
>	return (29);       /* 29 days in Feb in leap year */
>     else
>	return (28);       /* 28 days if not              */
>  else{
>     if (month > 7)        /* Is it August -> December?   */
>	month++;           /* Invert even/odd state if so */
>     if (month & 1)        /* Odd months have 31 days     */
>	return (31);
>     else
>	return (30);       /* Even months have 30 days    */
>  }
>}

This is ok, but it is easier to use a table:

daysinmonth(month, year)
int month, year)
{
   int monthlengths[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

   if (month == 2 && leapyear(year)) {
      return (29);
   } else {
      return monthlengths[month - 1];
   }
}

(Comments are left as an exercise for the reader.)

Frank Adams                           ihnp4!philabs!pwa-b!mmintl!franka
Multimate International    52 Oakland Ave North    E. Hartford, CT 06108

levy@ttrdc.UUCP (Daniel R. Levy) (09/17/86)

In article <34@vianet.UUCP>, devine@vianet.UUCP (Bob Devine) writes:
>Dave Lewis writes:
>> leapyear (year)
>> int year;
>> {
>>   if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))
>>      return (1);
>>   else
>>      return (0);
>> }
>
>  While this works, it is overkill.  Unless you believe that your
>code will make it to the year 2100, a simple test for divisibility
>by 4 is sufficient.  If you really want an algorithm for all years,
>you then need to also test for years divisible by 4000...
>
...
>
>#define  isleapyear(year)   ((year)%4)
>
>Bob Devine

Butbutbut... whattabout code which deals with things that are of concern
over more than a few years' scope, such as positions of the heavenly bodies?
The code needn't last till the year 2100 to want to deal with things that
are going to happen then (only the printouts :-) ).  And what about birth
dates of very old people?
-- 
 -------------------------------    Disclaimer:  The views contained herein are
|       dan levy | yvel nad      |  my own and are not at all those of my em-
|         an engihacker @        |  ployer or the administrator of any computer
| at&t computer systems division |  upon which I may hack.
|        skokie, illinois        |
 --------------------------------   Path: ..!{akgua,homxb,ihnp4,ltuxa,mvuxa,
	   go for it!  			allegra,ulysses,vax135}!ttrdc!levy

levy@ttrdc.UUCP (Daniel R. Levy) (09/17/86)

In article <886@ihdev.UUCP>, pdg@ihdev.UUCP (P. D. Guthrie) writes:
>>Or if you want a simple cpp macro:
>>
>>#define  isleapyear(year)   ((year)%4)
>>
>>Bob Devine
>
>Or if you want a complex cpp macro
>to return the number of days in a year......
>
>#define num_day(x) ((x)%4 ? (x)%100 ? (x)%400 ? 366 : 355 : 356 : 355)
                                                       ^^^   ^^^   ^^^

Huh?
-- 
 -------------------------------    Disclaimer:  The views contained herein are
|       dan levy | yvel nad      |  my own and are not at all those of my em-
|         an engihacker @        |  ployer or the administrator of any computer
| at&t computer systems division |  upon which I may hack.
|        skokie, illinois        |
 --------------------------------   Path: ..!{akgua,homxb,ihnp4,ltuxa,mvuxa,
	   go for it!  			allegra,ulysses,vax135}!ttrdc!levy

ron@brl-sem.ARPA (Ron Natalie <ron>) (09/17/86)

In article <886@ihdev.UUCP>, pdg@ihdev.UUCP (P. D. Guthrie) writes:
> to return the number of days in a year......
> 
> #define num_day(x) ((x)%4 ? (x)%100 ? (x)%400 ? 366 : 355 : 356 : 355)

Say what?  My calendar never has 355 or 356 days a year.  

rcd@nbires.UUCP (Dick Dunn) (09/18/86)

In article <1193@ttrdc.UUCP>, levy@ttrdc.UUCP (Daniel R. Levy) writes:
> In article <34@vianet.UUCP>, devine@vianet.UUCP (Bob Devine) writes:
[example of leap-year test covering 4, 100, 400]
> >  While this works, it is overkill.  Unless you believe that your
> >code will make it to the year 2100, a simple test for divisibility
> >by 4 is sufficient...
> Butbutbut... whattabout code which deals with things that are of concern
> over more than a few years' scope, such as positions of the heavenly bodies?

Butbutbut...the argument to the procedure was an int year, apparently CE.
So if you're worrying about getting it absolutely correct, don't forget the
Gregorian correction!  Leap years are child's play next to that one.

No, really, look:  FIRST, decide the domain for the function.  THEN design
the function to work well in that domain.  IF someone tells you that the
function doesn't work for his problem (outside the domain of the function),
tell him that hammers don't work for sawing wood, either.
-- 
Dick Dunn    {hao,ucbvax,allegra,seismo}!nbires!rcd	(303)444-5710 x3086
   ...Are you making this up as you go along?

karl@haddock (09/18/86)

vianet!devine (Bob Devine) writes:
>Dave Lewis writes:
>>   if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))
>
>While this works, it is overkill.  Unless you believe that your
>code will make it to the year 2100, a simple test for divisibility
>by 4 is sufficient.

It looks like you're assuming the test is for the current year.  For a
general-purpose algorithm (as per the original request), Dave's code is
correct.  If the input is a 32-bit signed integer measuring seconds since
1970 (a common implementation of time_t), then the range is contained in
(1900,2100) and the simpler algorithm works -- but this is probably not a
good assumption to make; an unsigned long will reach past 2100.

>If you really want an algorithm for all years,
>you then need to also test for years divisible by 4000...

I believe the quadrimillennium correction was never officially adopted.
It's not accounted for in cal(1).

Karl W. Z. Heuer (ima!haddock!karl; karl@haddock.isc.com), The Walking Lint

franka@mmintl.UUCP (Frank Adams) (09/19/86)

In article <886@ihdev.UUCP> pdg@ihdev.UUCP (55224-P. D. Guthrie) writes:
>Or if you want a complex cpp macro
>to return the number of days in a year......
>
>#define num_day(x) ((x)%4 ? (x)%100 ? (x)%400 ? 366 : 355 : 356 : 355)

If you want a macro which gives the right answer, you might instead try:

#define num_day(x) ((x)%4 ? (x)%100 ? (x)%400 ? 366 : 365 : 366 : 365)

Frank Adams                           ihnp4!philabs!pwa-b!mmintl!franka
Multimate International    52 Oakland Ave North    E. Hartford, CT 06108

wyatt@cfa.UUCP (Bill Wyatt) (09/19/86)

[ mass quantities of simple vs. complete leap year calculations arguments ]
> 
> No, really, look:  FIRST, decide the domain for the function.  THEN design
> the function to work well in that domain.  IF someone tells you that the
> function doesn't work for his problem (outside the domain of the function),
> tell him that hammers don't work for sawing wood, either.

Trouble is, in the real world, your function will be re-used outside its
proper domain, and by someone who has no idea your function is even needed,
much less not suited for his/her application. If the cost of doing it right
is small, then do it right. The trouble with that sentence is the problem of
determining the cost of not doing it right.
-- 

Bill    UUCP:  {seismo|ihnp4}!harvard!talcott!cfa!wyatt
Wyatt   ARPA:  wyatt%cfa.UUCP@harvard.HARVARD.EDU

karl@haddock (09/22/86)

cfa!wyatt (Bill Wyatt) writes:
>If the cost of doing it right is small, then do it right.

I knew someone who wrote a calendar program, complete with quadrimillennium
correction, in assembly language on a machine that was scheduled to be
junked in a month!

Karl W. Z. Heuer (ima!haddock!karl or karl@haddock.isc.com), The Walking Lint

guido@mcvax.uucp (Guido van Rossum) (09/23/86)

Wouldn't Jim Cottrell love this:

	#define leapyear(y) !((y)&3)

	daysinmonth(m, y) {
		if (m == 2) return 28 + leapyear(y);
		return 30 + ((1&m) ^ (m>7));
	}

Seriously, I believe that the original code, which applied the same rule
here showed a discrepancy between its seemingly clear coding style
(explaining every simple statement with a comment) and the 'trick' in
the algorithm (invert the parity of the month for months > 7).

In my eyes the solution which uses a table is the cleanest.

devine@vianet.UUCP (Bob Devine) (09/25/86)

haddock!karl (Karl W. Z. Heuer) writes:
[ that my suggestion of only checking if the year is divisible by
four assumes "the test is for the current year" and that "For a general
purpose algorithm (as per the original request), Dave's code is correct."]

  Not quite right.  I said just testing by use of "year % 4" is perfectly
fine for most uses (this is sort of like generic products.)  However, if
you want to test for years outside the range of 1901-2099 it doesn't work.
The UNIX value returned by the time() system call only covers the years
1970 +/- 67 years which falls nicely in the range.  The time() call is
unlikely to change.  If your system doesn't do it this way or you need a
larger range, don't use the simple test!

  A general purpose algorithm must also take care of years before the Gregorian
calendar system was adopted by a country.  Any KGB agents out there that
are reading this be sure to handle the years before 1918 differently :-)
So, the posted general purpose algorithm is not comprehensive in its
handling of years.  My initial posting simply indicated that if you are
not going to comprehensive, at least be a little faster.

> I believe the quadrimillennium correction was never officially adopted.
> It's not accounted for in cal(1).

  I'll check into the official rules; 'cal' is not a definite source. 
The Gregorian calender is about 1/2 minute too long compared to the solar
calendar which was quite close considering it was done by a 16th century
astronomer.  That means that there needs to be a day added in, oh, about
3,000 years.  Be sure to mark the date :-)


Bob Devine

henry@utzoo.UUCP (Henry Spencer) (09/25/86)

> ...If you really want an algorithm for all years,
> you then need to also test for years divisible by 4000...

No, there is no 4000-year correction.  There's 4 and 400 and that's it
at present.  Some twiddling will eventually become necessary, but it
won't be anything as simple and uniform as leap millenia -- the function
we are trying to approximate is not linear on that time scale.
-- 
				Henry Spencer @ U of Toronto Zoology
				{allegra,ihnp4,decvax,pyramid}!utzoo!henry

fgd3@jc3b21.UUCP (Fabbian G. Dufoe) (09/29/86)

In article <86900058@haddock>, karl@haddock writes:
> 
> vianet!devine (Bob Devine) writes:
> >Dave Lewis writes:
> >>   if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))
> >
> >While this works, it is overkill.  Unless you believe that your
> >code will make it to the year 2100, a simple test for divisibility
> >by 4 is sufficient.

It is particularly galling to see a correct algorithm criticized as
overkill when it is as simple and short as the above code segment.  There
may be a justification for writing code that only works part of the time if
the fix is costly and difficult.  However, it should be a general rule that
an algorithm which works in all cases is preferred over one that only works
in most cases.

Instead of panning someone's code because he has written it more correctly,
one should adopt the improved algorithm with gratitude.

Fabbian Dufoe
  350 Ling-A-Mor Terrace South
  St. Petersburg, Florida  33705
  813-823-2350

UUCP: ...akgua!akguc!codas!peora!ucf-cs!usfvax2!jc3b21!fgd3