[comp.lang.c] Request for simple date routines.

stone@mars.mpr.ca (Darren Stone) (04/10/91)

Hi.  Just a simple request I hope somebody can
easily pull out of their toolbox...

(1)
I'd like a function which, given a date, returns
0..6 representing the day of the week of the date.

(2)
I'd also like a function which, given 2 dates,
returns the number of days between them.

I really don't care at all about efficiency, but
they must work absolutely reliably for +/- several
hundred years (taking into account the leap-year
rules).  Actually, what are the leap year rules?
Something about evenly divisible by 4, except
not by 100, except by 400?

Thanx in advance.

- Darren -

jhz@cunixa.cc.columbia.edu (Jennifer H. Zemsky) (04/15/91)

In article <1991Apr9.234255.143@mprgate.mpr.ca> stone@mars.mpr.ca (Darren Stone) writes:
>
>
>I really don't care at all about efficiency, but
>they must work absolutely reliably for +/- several
>hundred years (taking into account the leap-year
>rules).  Actually, what are the leap year rules?
>Something about evenly divisible by 4, except
>not by 100, except by 400?
>
can't help with the first two, but the rule of leap years is:
leap = (((year/4 is int) && !(year/100 is int)) || (year/400 is int))
this is why many electric calendars go from 1901 - 2099 ... it saves the
check of year/100.

good luck.


>- Darren -

phil@ux1.cso.uiuc.edu (Phil Howard KA9WGN) (04/15/91)

stone@mars.mpr.ca (Darren Stone) writes:

>Hi.  Just a simple request I hope somebody can
>easily pull out of their toolbox...

>(1)
>I'd like a function which, given a date, returns
>0..6 representing the day of the week of the date.

The following is written on the fly and NOT TESTED.  All good programmers
test everything before using it anyway, so why should I be worried.
You might want to also add validity checking, especially for user input.

For a year from 1901 to 2099, subtract 1900 from the year giving 1 to 199:
	yy = year - 1900;
Now add the year value divided by 4:
	yy += ( yy / 4 );
Now add a special number for the month:
	int mnums[12] = { 0,3,3,6,1,4,6,2,5,0,3,5 };
	yy += mnums[ month-1 ]; /* month is 1..12 */
Special case for leap years:
	if ( (year%4)==0 && month<3 ) yy -= 1;
Add the date (1..31):
	yy += date;
Now just take the number modulo 7:
	char *weekday[7] = { "sun","mon","tue","wed","thu","fri","sat" };
	day = weekday[ yy%7 ];

>I really don't care at all about efficiency, but
>they must work absolutely reliably for +/- several
>hundred years (taking into account the leap-year
>rules).  Actually, what are the leap year rules?
>Something about evenly divisible by 4, except
>not by 100, except by 400?

It gets more complicated beyond this range.  The leap year rules for the
100/400/4000 years is:
    Years divisible by 100 are NOT leap years UNLESS they are divisible
    by 400 and not 4000.  Thus 2000 and 2400 are leap years and 2100 and
    4000 are not.
-- 
 /***************************************************************************\
/ Phil Howard -- KA9WGN -- phil@ux1.cso.uiuc.edu   |  Guns don't aim guns at  \
\ Lietuva laisva -- Brivu Latviju -- Eesti vabaks  |  people; CRIMINALS do!!  /
 \***************************************************************************/

imc@prg.ox.ac.uk (Ian Collier) (04/19/91)

I don't know whether you've had an answer or not (I haven't read one
in this newsgroup), so...

In article <1991Apr9.234255.143@mprgate.mpr.ca>, stone@mars.mpr.ca (Darren Stone) wrote:
>Hi.  Just a simple request I hope somebody can
>easily pull out of their toolbox...

Well,... I've written date routines before (in REXX & BASIC, of all
languages!), and most of them have turned out to be wrong. But I
just fixed one of them, so it should be OK now. With any luck :-)

>I'd like a function which, given a date, returns
>0..6 representing the day of the week of the date.

>I'd also like a function which, given 2 dates,
>returns the number of days between them.

>I really don't care at all about efficiency, but
>they must work absolutely reliably for +/- several
>hundred years.

They should work for any date since the invention of the modern calendar.
However, since I have no real way of telling whether May 21st 1832 was
actually a Monday, I can't guarantee it :-)

I solved both problems by assigning a number to each date (essentially the
number of days since Jan 1, 1 A.D., although of course that date is
meaningless in practice). So if you have dates d1 and d2, which give rise
to numbers n1 and n2, then there are n2-n1 days between them, and moreover
n1 mod 7 gives you the day of the week of d1. You don't need to store the
dates really, just the numbers.

First, define a couple of functions.

let leap(y) = (y%4==0) && (y%100!=0) || (y%400==0)
let days(y) = ((y-1)*1461)/4 - (3+3*((y-1)/100))/4 /*long integer arithmetic*/

which is (I hope) equivalent to int(365.25*(y-1)) + int(-.75*int((y-1)/100))
where int(x) is the greatest integer which is no more than x, so that days(y)
is the number of days before year y (the right-hand half accounts for the
leap year rule for multiples of 100). This is the really-important-function :-)

Let an array m indexed from 0 to 11 hold the number of days in each month.

To turn a date (day,month,year) into a number n, do:

if(year<100) year+=1900;
n=days(year)+day-1;
m(1)=28+leap(year);
for(x=0;x<month-1;n+=m(x++));

To turn a number back into a date (weekday,day,month,year), do:

weekday=n%7;     /* 0-6 -> Monday - Sunday */
year=(n*4)/1461 +1;
day=n-days(year);
while(d>=365+leap(year)) day-= 365=leap(year++);
m(1)=28+leap(year);
day++;
for(month=0;day>m(month);day-=m(month++));
month++;

The maximum number of times around the while loop is 2, at least within the
next few millenia :-)

Ian Collier
Ian.Collier@prg.ox.ac.uk | imc@ecs.ox.ac.uk