/*
** scalar date routines    --    public domain by Ray Gardner
** These will work over the range 1/01/01 thru 14699/12/31
*/

static long jan1date;

#include "date\scaldate.hpp"

int isleap (unsigned yr)
{
   return yr % 400 == 0 || (yr % 4 == 0 && yr % 100 != 0);
}

static unsigned months_to_days (unsigned month)
{
   return (month * 3057 - 3007) / 100;
}

static long years_to_days (unsigned yr)
{
   return yr * 365L + yr / 4 - yr / 100 + yr / 400;
}

long ymd_to_scalar (unsigned yr, unsigned mo, unsigned day)
{
   long scalar;
   scalar = day + months_to_days(mo);
   if ( mo > 2 )                         /* adjust if past February */
      scalar -= isleap(yr) ? 1 : 2;
   yr--;
   scalar += years_to_days(yr);
   return scalar;
}

void scalar_to_ymd (long scalar, unsigned *yr, unsigned *mo, unsigned *day)
{
   unsigned n;                /* compute inverse of years_to_days() */

   for ( n = (unsigned)((scalar * 400L) / 146097); years_to_days(n) < scalar;)
      n++;                          /* 146097 == years_to_days(400) */
   *yr = n;
   n = (unsigned)(scalar - years_to_days(n-1));
   if ( n > 59 ) {                       /* adjust if past February */
      n += 2;
      if ( isleap(*yr) )
         n -= n > 62 ? 1 : 2;
   }
   *mo = (n * 100 + 3007) / 3057;    /* inverse of months_to_days() */
   *day = n - months_to_days(*mo);
}

/*
**  Determine if a given date is valid
*/

int valiDate(unsigned yr, unsigned mo, unsigned day)
{
      unsigned int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

      if (1 > mo || 12 < mo)
            return 0;
      if (1 > day || day > (days[mo - 1] + (2 == mo && isleap(yr))))
            return 0;
      else  return 1;
}

/*
**  Return the day of the week
*/

int dow(unsigned yr, unsigned mo, unsigned day)
{
 
#if (!ISO)        /* Sunday(0) -> Saturday(6) (i.e. U.S.) calendars  */
      return (ymd_to_scalar(yr, mo, day) % 7L);
#else             /* International Monday(0) -> Sunday(6) calendars  */
      return ((ymd_to_scalar(yr, mo, day) - 1L) % 7L);
#endif
}

/*
**  Return the day of the year (1 - 365/6)
*/

int daynum(int year, int month, int day)
{
      jan1date = ymd_to_scalar(year, 1, 1);
      return (int)(ymd_to_scalar(year, month, day) - jan1date + 1L);
}

/*
**  Return the week of the year (1 - 52, 0 - 52 if ISO)
*/

int weeknum(int year, int month, int day)
{
      int wn, j1n, dn = daynum(year, month, day);

      dn += (j1n = (int)((jan1date - (long)ISO) % 7L)) - 1;
      wn = dn / 7;
      if (ISO)
            wn += (j1n < 4);
      else  ++wn;
      return wn;
}

#ifdef TEST

#include <stdio.h>
#include <stdlib.h>

void do_err(void);

void main(int argc, char *argv[])
{
      int day, month, year;
      char *days[] =
#if (!ISO)
            {"Sunday", "Monday", "Tuesday", "Wednesday",
             "Thursday", "Friday", "Saturday"};
#else
            {"Monday", "Tuesday", "Wednesday", "Thursday",
             "Friday", "Saturday", "Sunday"};
#endif

      if (4 > argc)
      {
            puts("Usage: DAYNUM month day year");
            return;
      }

      month = atoi(argv[1]);
      day   = atoi(argv[2]);
      year  = atoi(argv[3]);
      if (100 > year)
            year += 1900;

      if (!valiDate(year, month, day))
            printf("%d/%d/%d is invalid!\n", month, day, year);
      else  printf("%d/%d/%d is a %s, day #%d in week %d\n", month, day, year,
                  days[dow(year, month, day)], daynum(year, month, day),
                  weeknum(year, month, day));
}

#endif /* TEST */
