/* This code comes from a posting by Tomas Rokicki to the old
 * comp.sys.amiga newsgroup in August of 1988.  Since it was publicly
 * posted and there is no copyright, I'm assuming it's ok to use this
 * code.  This is pretty clever stuff!
 *
 * I've renamed this function to compute_date(), and made it return
 * the year, month, day, and weekday number, rather than setting
 * globals.  I've also modified the code a little to do this, and to
 * use 0-based day, month, and weekday, and to compute day of year.
 * If it doesn't work, it's almost certainly my fault.
 *
 *
 * Here's Tomas' original opening comment:
 *
 *
 *   Written by Tomas Rokicki of Radical Eye Software.
 *
 *   Here's an updated ShowDate() routine, as requested by Rob
 *   Peck.  This one even has some limited explanations of how
 *   it works!  It sets the day of week as well as month, day,
 *   and year from the values returned by DateStamp().
 *
 *   Note that the original code published in Peck's book, would
 *   fail for dates prior to 1984.  When compiled with 16-bit
 *   integers, it would fail for dates after 2007.  The current
 *   code will work with any size integer from Amiga day 0 until
 *   March 1, 2100.
 */

#ifndef OLDTIME

/*
 *   This routine returns in *m, *d, *y, *w, and *jul the current
 *   month, day, year, day of week, and day of year.  Months, days,
 *   and day of weeks all start at 1 with first members January, the
 *   first, and Sunday, respectively.
 */
void compute_date(long n, int *y, int *m, int *d, int *w, int *jul)
{
    int tmp_y, tmp_m, ly;
/*
 *   Set n to the number of days since Amiga day -671, which is
 *   March 1, 1976.  It's easier to figure years starting in March,
 *   since then the lengths of the months are 31, 30, 31, 30, 31,
 *   31, 30, 31, 30, 31, 31, 28.  This is almost linear.
 */
   n += 671 ;
/*
 *   The easiest is the weekday.  This is simply the number of days
 *   modulo 7, corrected for the start date.  March 1, 1976 was a
 *   Monday, so we add 1 to get back to a Sunday, and take the modulo.
 */
   *w = (n + 1) % 7 ;
/*
 *   There are exactly 1461 days every four years, until 2100, which
 *   is the first year divisible by 4 that is not a leap year AA
 *   (After Amiga.)  This gives the years lengths of 365 (1976),
 *   365 (1977), 365 (1978), and 366 (1979).  Note that this is
 *   correct because we start our years in March, so 1979 is the
 *   leap year.
 */
   tmp_y = (4 * n + 3) / 1461 ;
/*
 *   We now subtract off the years (see them melt off her face.)
 *   We use a long constant for 16-bit systems.  Again we use the
 *   fact that the leap year is the fourth year, not the first.
 */
   n -= 1461L * tmp_y / 4 ;
/*
 *   Now we can adjust the year to the proper value by adding
 *   1976.
 */
   tmp_y += 1976 ;
/*
 * We're now in a position to calculate the day of the year.  At this
 * point, n is the day of the year with 1 March = 0.  To get the
 * actual day of year, we need to add the number of days in Jan and
 * Feb, and take this modulo the number of days in that year.  There
 * are 59 days in Jan and Feb, 60 in leap years, and 365 days per
 * year, 366 in leap years.  So lets figure out if this is a leap year
 * first.  (There is a catch here:  our idea of the year may be wrong.
 * Looking below, you see we increment the year if the month is Jan or
 * Feb.  Actually, though, that's what we want, because we only add
 * the extra day after Feb in a leap year.)
 */
   ly = (tmp_y%4) == 0;
   *jul = (n + 59 + ly) % (365 + ly);
/*
 *   We calculate the month.  Since we start in March, the length
 *   of the months are always 30 or 31, except for the last month,
 *   which is shorter.  This is fortunate, as it allows us to use
 *   a simple mathematical formula for the month.  The lengths of
 *   the months are (31, 30, 31, 30, 31), repeated three times and
 *   the end lopped off.  So, our slope is 153/5.  An intercept of
 *   2 gives us the 31 and 30 lengths.
 */
   tmp_m = (5 * n + 2) / 153 ;
/*
 *   And now we subtract off the months.
 */
   *d = n - (153 * tmp_m + 2) / 5 ;
/*
 *   Now we convert from March-based years back to January-based
 *   years.
 */
   tmp_m += 2 ;
/*
 *   And, if we've gone over 12, we increment the year.
 */
   if (tmp_m > 11) {
      tmp_y++ ;
      tmp_m -= 12 ;
   }
/*
 *   That's it!
 */
   *y = tmp_y;
   *m = tmp_m;
}


#else
/* Here's my old implementation.  It's easier to understand, but
 * bigger and slower.
 */

#define FOUR_YEARS (4*365+1)
/* Jan 1 is day 0, so Feb 29 is day 31+28 = 59 */
#define LEAP_DAY 59
/* weekday (Sunday=0) of 1-Jan-76 */
#define WKDAY_BIAS 4

void compute_date(long n, int *y, int *m, int *d, int *w, int *jul)
    {
	/* unsigned shorts are cheaper for maths than ints */
	unsigned short days = n + 365*2 + 1;
	unsigned short yr4 = days/FOUR_YEARS;
	unsigned short dy4 = days%FOUR_YEARS;
	unsigned short dy;
	
	*w = (days+WKDAY_BIAS)%7;	/* easy */
	if (dy4 == LEAP_DAY) {
	    *jul = dy4;
	    *d = 28;	/* 29 Feb, 0 origin */
	    *m = 1;	/* February, 0 origin */
	    *y = 1976 + 4*yr4;
	} else {
	    static unsigned char days_in_month[12] = {
		31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
	      };
	    unsigned char *ptr;
	    
	    if (dy4 > LEAP_DAY) --dy4;
	    /* now we can forget 29 Feb ever happens */
	    *y = 1976 + (dy4/365) + (yr4*4);
	    *jul = dy = dy4 % 365;
	    /* dy is now day of year */
	    
	    for (ptr=days_in_month; dy>=*ptr; ++ptr) dy -= *ptr;
	    *m = ptr-days_in_month;
	    *d = dy;
	}
    }
#endif
