/*
 * dayslib.c
 *
 * Small library to handle dates. The algorithms are mostly from Pascal's SWAG.
 *
 * History:
 *       1/11/03 Creation from the old sources of ohdatabank.
 *       8/01/04 Fix the yday2wday() function. Also generalize the formula
 *               for a wider range of years (that is, pass the 2099 barrier).
 *      15/09/06 Fix some comments, protect the macro GetYearDays.
 *
 * Author: Dario Rodriguez dario@softhome.net
 * This code is dual licensed under the terms of the GNU LGPL/3-clause BSD.
 */

/* Constants */
const int __NAcDaysNoLeap[13]=
        {0,31,59,90,120,151,181,212,243,273,304,334,365};
const int __NAcDaysLeap[13]=
        {0,31,60,91,121,152,182,213,244,274,305,335,366};

int *
GetYearDays(int year)
{
	return(((((year)%4)==0 && !(((year)%100)==0)) \
                || ((year)%400)==0)?__NAcDaysLeap:__NAcDaysNoLeap);
}

/* Function prototypes */
int yday2mday(int year, int yday, int *month, int *day);
int mday2yday(int year, int month, int day, int *yday);
int yday2wday(int year, int yday, int *wday);

/* Function bodies */
/* yday2mday(): returns the day-in-month of the specified day-in-year */
int
yday2mday(int year, int yday, int *month, int *day)
{
        const int *NAcDays;
        NAcDays=GetYearDays(year);
        for(*month=1;yday>NAcDays[*month];(*month)++)
                ;
        *day=yday-NAcDays[(*month)-1];
        return(year);
}

/* yday2mday(): returns the day-in-year of the specified day-in-month */
int
mday2yday(int year, int month, int day, int *yday)
{
        const int *NAcDays;
        NAcDays=GetYearDays(year);
        *yday=NAcDays[month-1]+day;
        return(year);
}

/* yday2mday(): returns the weekday (0:sunday) of the specified day-in-year */
/* Warning: formula only valid for 1900- */
int
yday2wday(int year, int yday, int *wday)
{
#if 0
        /* Simplified formula, valid only 1901-2099 */
        *wday=(((year+3)/4)+year+4+yday)%7;
#else
        /* Full-blown formula, valid from 1900 onwards */
        /* (ok, it works for years earlier to 1900, but I'm not sure when */
        /* Also, it varies from country to country when they adopted the */
        /* current calendary... see your encyclopaedia for more info on this).*/
        *wday=(((year+3)/4)+year+4+yday-((year-1)/100-19)+((year-1)/400-4))%7;
#endif
        return(year);
}

#ifdef UNIT_TESTING
#include <stdio.h>
int
main(void)
{
        struct {
                int day;
                int month;
                int year;
                int wday;
        } Tests[]={{1,1,2003,3},{31,12,2003,3},{1,1,2004,4},{1,2,2004,0},{1,3,2004,1},
                   {1,1,2000,6},{1,1,2001,1},{1,1,2002,2},{1,1,2003,3},{1,1,2004,4},{1,1,2005,6},
                   {1,1,2099,4},{1,1,2100,5},{1,1,2101,6},{1,1,2102,0},{1,7,2087,2},
                   {1,1,1897,5},{1,1,1898,6},{1,1,1899,0},{1,1,1900,1},{1,1,1901,2},
                   {12,10,2008,0},{0}};
        long i;
        int day,month,year,wday;
        int resyday,reswday;
        for(i=0;Tests[i].day>=1;i++) {
                day=Tests[i].day,month=Tests[i].month;
                year=Tests[i].year,wday=Tests[i].wday;
                mday2yday(year,month,day,&resyday);
                yday2wday(year,resyday,&reswday);
                printf("%2i/%02i/%04i(%i) -> yday:%i,wday:%i... %s\n",day,month,year,wday,resyday,reswday,(wday!=reswday)?"ERROR":"Ok");
        }
        return(0);
}
#endif