/*  Day-Of-Week program, version 2.01, by Craig Bruce, 1995/08/11.
**
**  This code is Public Domain Software.
*/

#include <stdio.h>
#include <string.h>

int     main( int argc, char *argv[] );
void    TestConsecutiveAndInverse( void );
long    DayCount( int year, int month, int day );
int     NumberToDate( long dayNum, int *outYear, int *outMonth, int *outDay );
int     YearDayToDate( int year, int dayNum, int *outYear, int *outMonth,
            int *outDay );

/****************************************************************************/
int main( int argc, char *argv[] )
{
    int     year, month, day, sc, dow, invYear, invMonth, invDay, doy;
    long    dayCount;
    static char *dowNames[7] = {"Sunday", "Monday", "Tuesday", "Wednesday",
                                "Thursday", "Friday", "Saturday"};

    if (argc != 2) {
        fprintf(stderr, "usage: %s YYYY/MM/DD\n", argv[0]);
        exit( 1 );
    }
    if (argv[1][0]=='-' && argv[1][1]=='t') {
        TestConsecutiveAndInverse();
        return( 0 );
    }
    sc = sscanf(argv[1], "%d/%d/%d", &year, &month, &day);
    if (sc != 3) {
        fprintf(stderr, "the date you gave is not in the correct format!\n");
        exit( 1 );
    }
    dayCount = DayCount( year, month, day );
    if (dayCount==-1) {
        printf("The give date is invalid or before 1582/10/15\n");
    } else {
        dow = (dayCount+5) % 7;
        NumberToDate( dayCount, &invYear, &invMonth, &invDay );
        doy = dayCount-DayCount(year, 1, 1)+1;
        if (year == 1582) doy = dayCount + 288;
        printf( "%s%s%s (dayCount=%ld) (inv:%04d/%02d/%02d) (d.o.year=%d)\n",
            argv[1], " is a ", dowNames[dow], dayCount, invYear,
            invMonth, invDay, doy);
    }
    return( 0 );
}

/****************************************************************************/
void TestConsecutiveAndInverse( void )
{
    int year, month, day, outYear, outMonth, outDay;
    int dayCount, validDay;

    validDay = 0;
    for (year=1582; year<=9999; year++) {
        printf("testing year %d...\n", year);
        for (month=1; month<=12; month++) {
            for (day=1; day<=31; day++) {
                dayCount = DayCount( year, month, day );
                if (dayCount>=0) {
                    if (dayCount != validDay) {
                        printf("%s: %04d/%d/%d: dayCount=%ld, validDay=%ld\n"
                            ,"Consecutive error: ", year, month, day, dayCount,
                            validDay);
                        exit( 1 );
                    }
                    validDay += 1;
                    NumberToDate( dayCount, &outYear, &outMonth, &outDay );
                    if (outYear!=year || outMonth!=month || outDay!=day) {
                        printf("Date-Inverse error: %d/%d/%d ret %d/%d/%d\n",
                            year, month, day, outYear, outMonth, outDay);
                        exit( 1 );
                    }
                }
            }
        }
    }
    printf("Test completed successfully\n");
}

/****************************************************************************\
**  DayCount(): written 1995/05/16 by Craig Bruce.
**
**  Returns the number of days between 1582/10/15 and the given date, where
**  calling with the epoch date would return zero.  If the given date is
**  invalid, before the epoch date, or after 9999/12/31, then a value of -1
**  is returned instead.  The epoch date was a Friday, according to the
**  Gregorian calendar, which is what this function uses.  Note that the
**  calendar used by Britian and its colonies did not synch with the
**  Gregorian calendar until 1752/09/14.
**
**  This function can be used to compute the day-of-week of a given date
**  by using the formula "(returnValue+5)%7", which gives Sunday as zero;
**  this function can be used to count the number of days between two
**  different dates by subtracting the returned values; and this function
**  can be used simply to check the validity of a date (like 2000/02/29).
\****************************************************************************/
static int cumDayInYear[12+1] 
    = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};

long DayCount( int year, int month, int day )
{
    int     leap;
    long    n;

    if (year<1582 || year>9999) return( -1 );
    if (month<1 || month>12 || day<1 || day>31) return( -1 );
    n = (year * 365L) + ((year-1)/4) - ((year-1)/100) + ((year-1)/400);
    n = n + cumDayInYear[month-1] + (day-1);
    leap = (year%4==0 && year%100!=0) || year%400==0;
    if (leap && month>=3) n+=1;
    n -= 578100;
    if (n<0 || day>cumDayInYear[month]-cumDayInYear[month-1]) {
        if ( !(n>=0 && month==2 && leap && day==29) ) {
            return( -1 );
        }
    }
    return( n );
}

/****************************************************************************\
**  NumberToDate(): written 1995/08/04 by Craig Bruce.
**
**  Returns the year, month, and day of the given day number.  This is the
**  inverse of the DayCount() function.  The date must be between 1582/10/15
**  and 9999/12/31 inclusive or the function will return a value of -1
**  indicating an error (the regular return value is 0).
\****************************************************************************/
int NumberToDate( long dayNum, int *outYear, int *outMonth, int *outDay )
{
    long    n;
    int     year;

    *outYear = *outMonth = *outDay = 0;
    if (dayNum<0 || dayNum>3074323) return( -1 );
    n = dayNum + 578100;
    year = n/365 - n/550426;
    if (year>9999) year = 9999;
    n = DayCount( year, 1, 1 );
    if (dayNum < n) {
        year -= 1;
        n = DayCount( year, 1, 1 );
        if (dayNum < n) {
            year -= 1;
            n = DayCount( year, 1, 1 );
        }
    }
    if (year == 1582) n = -287;
    return( YearDayToDate( year, dayNum-n+1, outYear, outMonth, outDay ) );
}

/****************************************************************************\
**  YearDayToDate(): written 1995/08/04 by Craig Bruce.
**
**  Returns the year, month, and day of the given day number of the given
**  year.  The date must be between 1582-288 and 9999-365 inclusive or the
**  function will return a value of -1 indicating an error (the regular
**  return value is 0).  Note that the year 1582 is a bit screwy in this
**  algorithm, since it assumes that Oct. 15 is the 288th day of that year,
**  which is not historically accurate.
\****************************************************************************/
int YearDayToDate( int year, int dayNum, int *outYear, int *outMonth,
    int *outDay )
{
    int     leap, n, extra, month;

    *outYear = *outMonth = *outDay = 0;
    if (year<1582 || year>9999 || (year==1582 && dayNum<288)) return( -1 );
    leap = (year%4==0 && year%100!=0) || year%400==0;
    n = (leap && dayNum>=60) ? dayNum-2 : dayNum-1;
    if (dayNum<1 || n>364) return( -1 );
    month = n/30;
    if (month>11) month=11;
    if (n<cumDayInYear[month]) month -= 1;
    if (n>=cumDayInYear[month+1]) month += 1;
    *outYear  = year;
    *outMonth = month+1;
    if (leap && month+1==2) {
        *outDay = dayNum - 32 + 1;
    } else {
        *outDay = n - cumDayInYear[month] + 1;
    }
    return( 0 );
}
