/* Date/time functions
   Rick Smereka, Copyright (C) 2002-2004.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, get a copy via the Internet at
   http://gnu.org/copyleft/gpl.html or write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston,
   MA 02111-1307 USA

   You can contact the author via email at rsmereka@future-lab.com

   Original Linux version. Moved function 'get_time' from 'parse.c'
   to this module. May/2002, Rick Smereka

   Ported to 32-bit Windows. Jun/2002, Rick Smereka

   Ported to DOS and QNX 4.x. Jul/2002, Rick Smereka

   Changed function 'get_time' to return a compact date/time
   string instead of using 'asctime'. Sep/2002, Rick Smereka

   Ported to Debian Linux. Oct/2002, Rick Smereka

   Added functions 'run_year' and 'get_ftime'. 
   Ported to Debian Linux. Nov/2002, Rick Smereka

   Added functions 'days_in_month' and 'isleap'.
   Jan/2004, Rick Smereka

   Added functions 'juldate' and 'datdif'. Dec/2004,
   Rick Smereka */

#include "stdhead.h"

// julian base year

#define DATIME_JUL_BASE 1954

/* month short names */

char *smonth[] = { "", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", 
                   "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" };

/* private function */

static int datime_get_ampm(char *, char *);

int datime_month_name_2_num(char *mname)
{
   /* Convert a month name to a month number. Only the first three
      characters of the month name are compared so you may use the
      long or short month names. January is considered month one.
      Function returns the month number upon success, zero otherwise. */

   int len, i;

   if (mname == (char *)NULL)
      return(FALSE);

   len = strlen(mname);

   /* must have a minimum of three characters */

   if (len < 3)
      return(FALSE);

   for(i = 1; i <= 12; i++)
      if (!strnicmp(smonth[i], mname, 3))
         return(i);

   return(FALSE);
}

int datime_month_num_2_name(int mnum, char *mname)
{
   /* Translate a month number (1=Jan) to the short month name.
      Function returns 'TRUE' upon success with the month name
      loaded into 'mname' (which must already be allocated to
      sufficient size by the caller), 'FALSE' otherwise. */

   if (mnum <= 0 || mnum > 12)
      return(FALSE);

   if (mname == (char *)NULL)
      return(FALSE);

   strcpy(mname, smonth[mnum]);
   return(TRUE);
}

void get_ftime(char *tbuf)
{
   /* Format and return the current time string. Returned format is:

         yyyy/mm/dd hh:mm:ss

      Buffer 'tbuf' must already be allocated by the caller
      to a minimum size of 20 bytes. */

   char raw_time[15], year[5], month[3], day[3], hour[3];
   char minute[3], second[3];

   get_time(raw_time);
   strncpy(year, raw_time, 4);
   year[4] = EOS;
   strncpy(month, &raw_time[4], 2);
   month[2] = EOS;
   strncpy(day, &raw_time[6], 2);
   day[2] = EOS;
   strncpy(hour, &raw_time[8], 2);
   hour[2] = EOS;
   strncpy(minute, &raw_time[10], 2);
   minute[2] = EOS;
   strncpy(second, &raw_time[12], 2);
   second[2] = EOS;
   sprintf(tbuf, "%s/%s/%s %s:%s:%s", year, month, day, hour, minute, second);
}
   
void get_time(char *tbuf)
{
   /* Format and return the current time string. Format of the time string
      is:

         yyyymmddhhmmss

      Buffer 'tbuf' must be allocated to at least 15 bytes by the caller. */

   time_t tod;
   struct tm *tmbuf;

   if (tbuf == (char *)NULL)
      return;

   tod = time(NULL);
   tmbuf = localtime(&tod);
   sprintf(tbuf, "%d%02d%02d%02d%02d%02d", tmbuf->tm_year + 1900,
           tmbuf->tm_mon + 1, tmbuf->tm_mday, tmbuf->tm_hour,
           tmbuf->tm_min, tmbuf->tm_sec);
}

int datime_hour12_2_hour24(char *intime, char *outime, int sep_flag)
{
   /* Convert a time string expressed in 12 hour format
      to a time string expressed in 24 format. The input
      time string ('intime') may be in any of the following
      formats:

         hh[:mm][:ss]p, hh[:mm][:ss] p, hh[:mm][:ss]pm, hh[:mm][:ss] pm
         hh[:mm][:ss]a, hh[:mm][:ss] a, hh[:mm][:ss]am, hh[:mm][:ss] am
         hh[:mm][:ss]m, hh[:mm][:ss] m, hh[:mm][:ss]n, hh[:mm][:ss] n

      Where 'p' and 'pm' are the same, 'a' and 'am' are also the
      same. A trailing 'm' indicates midnight and a trailing
      'n' indicates noon. When a trailing 'm' or 'n' is used,
      the time is assumed to be '12:00' regardless of any
      other hour/minute value. Spaces may or may not be present 
      after the time and before the 'am' or 'pm' designator. The 
      colon separator is required in the input time string. The flag
      'sep_flag' is used to indicate whether the output time
      string should have a colon separator. The minute and seconds fields
      are completly optional. The output time string always contains 
      two digits each for the hour, minute and optional seconds. 
      The hour and minute are always present in the output string.
      Seconds are only present in the output string if present in
      the input string. Function returns 'TRUE' with the converted 
      time loaded into 'outime' upon success. Function returns 
      'FALSE' otherwise. */

   char *new_inp, wrd[10], ap;
   int hr, min, sec, is_sec, nwords, len, i;

   if (intime == (char *)NULL || !strlen(intime))
      return(FALSE);

   if (outime == (char *)NULL)
      return(FALSE);

   outime[0] = EOS;
   len = strlen(intime);
   hr = min = sec = is_sec = 0;
   ap = EOS;

   if ((new_inp = (char *)malloc(len + 1)) == (char *)NULL)
      return(FALSE);

   /* remove all spaces from the input time string */

   strip_space(intime, new_inp);
   nwords = ll_words(new_inp, ':');

   if (nwords > 3)
      {
      free(new_inp);
      return(FALSE);
      }

   /* take apart input time string */

   for(i = 1; i <= nwords; i++)
      {
      if (!ll_word(new_inp, wrd, i, ':'))
         {
         free(new_inp);
         return(FALSE);
         }

      /* on the last part of the input time string, get
         the 'am'/'pm' designator */

      if (i == nwords)
         if (!datime_get_ampm(wrd, &ap))
            {
            free(new_inp);
            return(FALSE);
            }

      switch(i)
         {
         case 1:
            /* hour */

            if (!qatoi(wrd, &hr))
               {
               free(new_inp);
               return(FALSE);
               }

            break;

         case 2:
            /* minute */

            if (!qatoi(wrd, &min))
               {
               free(new_inp);
               return(FALSE);
               }

            break;

         case 3:
            /* second */

            if (!qatoi(wrd, &sec))
               {
               free(new_inp);
               return(FALSE);
               }

            is_sec = TRUE;
            break;
         };
      }
       
   // printf("hr=%d,min=%d,sec=%d,is_sec=%d,ap=%c\n", hr, min, sec, is_sec, ap);
   free(new_inp);

   /* build 24hr time */
   /* first, adjust time */

   switch(ap)
      {
      case 'A':
         if (hr == 12)
            hr = 0;

         break;

      case 'P':
         /* adjust hour if necessary */

         if (hr != 12)
            hr += 12;

         break;

      case 'M':
         /* midnight is always hour zero, minute zero */

         hr = min = 0;
         break;

      case 'N':
         /* noon is always hour 12, minute zero */

         hr = 12;
         min = 0;
         break;
      };

   if (sep_flag)
      if (is_sec)
         sprintf(outime, "%02d:%02d:%02d", hr, min, sec);
      else
         sprintf(outime, "%02d:%02d", hr, min);
   else
      if (is_sec)
         sprintf(outime, "%02d%02d%02d", hr, min, sec);
      else
         sprintf(outime, "%02d%02d", hr, min);

   return(TRUE);
}

static int datime_get_ampm(char *wrd, char *ap)
{
   /* Get the 'am', 'pm' designator in the time string 'wrd'.
      If found, the designator will be removed from 'wrd' and
      a single character will be loaded into 'ap'. Acceptable
      designators are 'a', 'am', 'p', 'pm', 'n' and 'm'.
      Function returns 'TRUE' upon success, 'FALSE' otherwise. */

   char ch;
   int len, i, pos;

   if (wrd == (char *)NULL || !strlen(wrd))
      return(FALSE);

   if (ap == (char *)NULL)
      return(FALSE);

   len = strlen(wrd);
   *ap = EOS;

   /* loop to identify the designator */

   for(i = 0, pos = 0; i < len; i++)
      {
      ch = toupper(wrd[i]);

      if (ch >= '0' && ch <= '9')
         continue;

      /* set position of first non-digit */

      if (!pos)
         pos = i;

      /* if it's a space, ignore */

      if (ch == ' ')
         continue;

      /* load first character of designator */

      *ap = ch;
      break;
      }

   /* error on no designator */

   if (*ap == EOS)
      return(FALSE);

   /* designator must be on of 'a', 'p', 'm' or 'n' */

   if (*ap != 'A' && *ap != 'P' && *ap != 'M' && *ap != 'N')
      {
      *ap = EOS;
      return(FALSE);
      }

   /* elimiate designator */

   wrd[pos] = EOS;
   return(TRUE);
}

int run_year(void)
{
   /* Get the year of program run via 'get_time' function.
      Function returns year as a number upon success,
      zero otherwise. */

   char tbuf[20], year_char[5];
   int year;

   get_time(tbuf);
   strncpy(year_char, tbuf, 4);
   year_char[4] = EOS;

   if (!qatoi(year_char, &year))
      return(0);

   return(year);
}

int days_in_month(int year, int month)
{
   /* Determine the number of days in a month taking into account
      leap years. Function returns the number of days in 'month'
      upon success, zero otherwise. */

   static int numdays[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
   int leap;
   
   if (month < 1 || month > 12)
      return(0);

   leap = isleap(year);

   if (month == 2 && leap)
      return(29);

   return(numdays[month]);
}

int isleap(int year)
{
   /* Determine whether the 'year' is a leap year. Function returns
      'TRUE' if the 'year' is a leap year, 'FALSE' otherwise. */

   return((year % 4 == 0 && year % 100 != 0) || year % 400 == 0);
}

int juldate(char *dte, int *julout)
{
   /* Convert a date in the form 'yyyymmdd' to a Julian
      value using 'DATIME_JUL_BASE' as the base year. Function
      returns 'TRUE' upon success with the julian date
      value loaded into 'julout' upon success. Function
      returns 'FALSE' otherwise. */

   char tmp[5];
   int d_year, d_mo, d_day, ret, i;

   if (dte == (char *)NULL || strlen(dte) != 8)
      return(FALSE);

   if (julout == (int *)NULL)
      return(FALSE);

   *julout = 0;

   strncpy(tmp, dte, 4);
   tmp[4] = EOS;

   if (!qatoi(tmp, &d_year))
      return(FALSE);

   strncpy(tmp, &dte[4], 2);
   tmp[2] = EOS;

   if (!qatoi(tmp, &d_mo))
      return(FALSE);

   strncpy(tmp, &dte[6], 2);
   tmp[2] = EOS;

   if (!qatoi(tmp, &d_day))
      return(FALSE);

   ret = d_day;

   // for every month preceding the current, add days

   for(i = 1; i < d_mo; i++)
      ret += days_in_month(d_year, i);

   // add days from preceding years

   for(i = DATIME_JUL_BASE; i < d_year; i++)
      if (isleap(i))
         ret += 366;
      else
         ret += 365;

   *julout = ret;
   return(TRUE);
}

int datdif(char *d1, char *d2, int *dif)
{
   /* Find the number of days difference between two dates.
      'd1' is assumed to be the later of the two dates.
      Dates are expected in the form 'yyyymmdd'.
      Function returns 'TRUE' upon success with the number
      of days difference loaded into 'dif' upon success.
      Function returns 'FALSE' otherwise. */

   int jul1, jul2;

   if (d1 == (char *)NULL || d2 == (char *)NULL || dif == (int *)NULL)
      return(FALSE);

   if (!juldate(d1, &jul1))
      return(FALSE);

   if (!juldate(d2, &jul2))
      return(FALSE);

   *dif = jul1 - jul2;
   return(TRUE);
}

