#include "math.h"
#include "dbhost.h"   /* PROTOTYPE_ALLOWED */
#include "dbext.h"    /* YMDHMS_TIME_TYPE, ULONG, etc., round */
#include "time_.h"
#include "ioserv.h"   /* yr4digit() */
/*****************************************************************************
*                                                                            *
*      COMMON OCEANOGRAPHIC DATA ACCESS SYSTEM (CODAS)                       *
*                                                                            *
*      WRITTEN BY:  RAMON CABRERA, ERIC FIRING, and JULIE RANADA             *
*                   JOINT INSTITUTE FOR MARINE AND ATMOSPHERIC RESEARCH      *
*                   1000 POPE ROAD  MSB 404                                  *
*                   HONOLULU, HI 96822                                       *
*                                                                            *
*      VERSION:     3.00                                                     *
*                                                                            *
*      DATE:        APRIL 1989                                               *
*                                                                            *
*****************************************************************************/
/*-----------------------------------------------------------------------------

       FILE:  time_.c

       TIME CONVERSION & PRINTING FUNCTIONS

*/

int CUM_MONTH_DAYS[] =
   {0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};

/*-----------------------------------------------------------------------------

   FUNCTION:  invalid_time

      It checks a YMDHMS_TIME_TYPE structure for valid ranges.

   PARAMETER:

      t = pointer to YMDHMS_TIME_TYPE structure

   RETURNS:  0 if time is okay
             1 otherwise

   CALLED FROM:  C

*/
#if PROTOTYPE_ALLOWED
int invalid_time(YMDHMS_TIME_TYPE *t)
#else
int invalid_time(t)
YMDHMS_TIME_TYPE *t;
#endif
{
   if ((t->year < 1) || (t->year > 4095) ||
      (t->month < 1) || (t->month > 12) ||
      (t->day < 1) || (t->day > 31) ||
      (t->hour > 23) ||
      (t->minute > 59) ||
      (to_hundredths_sec_s(t->second) > 5999))
      return(INVALID_TIME);
   return(0);
}

/*-----------------------------------------------------------------------------

   FUNCTION:  TIMMIN

      Given a pointer to a ymdhms_time structure it returns the time
      in minutes since "JANUARY 1, 1  at 00:00"

      Note:  no validity check is done on t

   PARAMETER:  

      t = pointer to struct/array of type YMDHMS_TIME_TYPE

   RETURNS:  minutes from "JAN 1, 1 at 00:00"

   CALLED FROM:  C, FORTRAN

*/
#if PROTOTYPE_ALLOWED
ULONG TIMMIN(YMDHMS_TIME_TYPE *t)
#else
ULONG TIMMIN(t)
YMDHMS_TIME_TYPE *t;
#endif
{
   ULONG l_1440 = 1440, l_365 = 365;
   ULONG m, y;

   m = l_1440 * (CUM_MONTH_DAYS[t->month] + t->day - 1)
      + 60 * t->hour + t->minute;
   if ((y = t->year - 1) > 0)
      m += l_1440 * (l_365 * y + y / 4 - y / 100 + y / 400);
   if (!(t->year % 4) && ((t->year % 100) ||
      !(t->year % 400)) && t->month > 2)
   m += l_1440;
   return(m);
}

/*-----------------------------------------------------------------------------

   FUNCTION:  MINTIM

      Given time in minutes since Jan 1, 1 at 00:00, it calculates the 
      YMDHMS_TIME_TYPE equivalent.

   PARAMETERS:

      t = pointer to YMDHMS_TIME_TYPE struct/array

      m = pointer to LONG variable where the minutes are stored

   RETURNS:  VOID

   CALLED FROM:  C, FORTRAN

*/
#if PROTOTYPE_ALLOWED
void MINTIM(YMDHMS_TIME_TYPE *t, LONG *m)
#else
void MINTIM(t, m)
YMDHMS_TIME_TYPE *t;
LONG *m;
#endif
{
   LONG d, y, y1, im, d1, l_1440 = 1440, l_365 = 365;

   t->second = 0;
   t->minute = *m % 60;
   t->hour = (*m / 60) % 24;
   d = *m / l_1440;
   y = d / 365.2425;
   d1 = y * l_365 + y / 4 - y / 100 + y / 400;
   if (d > d1)
   {
      d -= (d1 - 1);
      y++;
   }
   else
   {
       y1 = y - 1;
       d -= (y1 * l_365 + y1 / 4 - y / 100 + y / 400 - 1);
   }
   if (d > l_365)
      if ((y % 4) || (!(y % 100) && (y % 400)))
      {
         d -= l_365;
         y++;
      }
      else if (d > 366)
      {
         d -= 366;
         y++;
      }
   if (!(y % 4) && ((y % 100) || !(y % 400)))
      if (d <= 60)
      {
         if (d > 31)
         {
            im = 2;
            d -= 31;
         }
         else im = 1;
      }
      else
      {
         im = (--d) / 29;
         if (d > CUM_MONTH_DAYS[im + 1]) im++;
         d -= CUM_MONTH_DAYS[im];
      }
   else
   {
      im = d / 29;
      if (d > CUM_MONTH_DAYS[im + 1]) im++;
      d -= CUM_MONTH_DAYS[im];
   }
   t->year = y;
   t->month = im;
   t->day = d;
   return;
}

/*-----------------------------------------------------------------------------

   FUNCTION:  TIMDIF

      Given pointers to two YMDHMS_TIME_TYPE structures, it returns the 
      difference in seconds between the second and the first times.

      Note:  no validity check is done on t1 and t2

   PARAMETERS:

      t1 = pointer to first time

      t2 = pointer to second time

   RETURNS: (t2 - t1) in seconds

   CALLED FROM:  C, FORTRAN

*/
#if PROTOTYPE_ALLOWED
LONG TIMDIF(YMDHMS_TIME_TYPE *t1, YMDHMS_TIME_TYPE *t2)
#else
LONG TIMDIF(t1, t2)
YMDHMS_TIME_TYPE *t1, *t2;
#endif
{
  /* 1999/07/13 Pierre Jaccard
     Added a test to check if seconds are in hun or not. This is a påroblem if
     one of the time is and the other is not.

     Original code was:

     return((TIMMIN(t2) - TIMMIN(t1)) * 60 +
     ((long)t2->second - (long)t1->second));

     New code is:
  */
  USHORT s1, s2;

  s1 = (t1->second & 0x8000) ? (t1->second & ~0x8000)/100 : t1->second;
  s2 = (t2->second & 0x8000) ? (t2->second & ~0x8000)/100 : t2->second;
 
  return((TIMMIN(t2) - TIMMIN(t1)) * 60 + ((long)s2 - (long)s1));
}

/*-----------------------------------------------------------------------------

   FUNCTION:  DIFTIM

      Given a reference YMDHMS_TIME_TYPE t1 and a number of seconds s from
      that time, this function calculates the YMDHMS_TIME_TYPE t2 as t1 + s.

      Note:  no validity check is done on t1 and t2

   PARAMETERS:  

      t1 = pointer to reference YMDHMS_TIME_TYPE

      t2 = pointer to resulting YMDHMS_TIM_TYPE

      s = pointer to number of seconds from reference time t1

   RETURNS:  VOID

   CALLED FROM:  C, FORTRAN

*/
#if PROTOTYPE_ALLOWED
void DIFTIM(YMDHMS_TIME_TYPE *t1, YMDHMS_TIME_TYPE *t2, LONG *s)
#else
void DIFTIM(t1, t2, s)
YMDHMS_TIME_TYPE *t1, *t2; 
LONG *s;
#endif
{
   LONG m, ss;

   ss = to_hundredths_sec_s(t1->second) / 100 + (*s);
   m = TIMMIN(t1) + div_pr(ss,60);
   MINTIM(t2, &m);
   t2->second = mod_pr(ss,60);
}

/*-----------------------------------------------------------------------------

   FUNCTION:  HTIMDIF

      Given pointers to two YMDHMS_TIME_TYPE structures, it returns the 
      difference in hundredths of seconds between the second and the first times.

      Note:  no validity check is done on t1 and t2
             MAXLONG can accommodate time differences of up to +/-68 years
             if expressed in seconds, 0.68 year or 248 days
             if in hundredths of seconds.  Since TIMDIF is used in the
             core database routines to perform calculations on profile
             times with respect to a block base time, this switch from returning
             a difference in seconds to one in hundredths of seconds
             restricts a block to contain a time range no more than
             248 days. - JR 95/09

   PARAMETERS:

      t1 = pointer to first time

      t2 = pointer to second time

   RETURNS: (t2 - t1) in hundredths of seconds

   CALLED FROM:  C, FORTRAN

*/
#if PROTOTYPE_ALLOWED
LONG HTIMDIF(YMDHMS_TIME_TYPE *t1, YMDHMS_TIME_TYPE *t2)
#else
LONG HTIMDIF(t1, t2)
YMDHMS_TIME_TYPE *t1, *t2;
#endif
{
   return((TIMMIN(t2) - TIMMIN(t1)) * 6000 +
      ((long)to_hundredths_sec_s(t2->second) -
       (long)to_hundredths_sec_s(t1->second)));
}

/*-----------------------------------------------------------------------------

   FUNCTION:  HDIFTIM

      Given a reference YMDHMS_TIME_TYPE t1 and a number of hundredths of
      seconds hs from that time, this function calculates the
      YMDHMS_TIME_TYPE t2 as t1 + hs.

      Note:  no validity check is done on t1 and t2
             Since hs is a signed value, we have to just set it to be
             expressed in hundredths of seconds (rather than do the
             usual sign bit test to determine if it is in seconds or
             hundredths of seconds).  - JR 95/09

   PARAMETERS:  

      t1 = pointer to reference YMDHMS_TIME_TYPE

      t2 = pointer to resulting YMDHMS_TIME_TYPE

      hs = pointer to number of hundredths of seconds from reference time t1

   RETURNS:  VOID

   CALLED FROM:  C, FORTRAN

*/
#if PROTOTYPE_ALLOWED
void HDIFTIM(YMDHMS_TIME_TYPE *t1, YMDHMS_TIME_TYPE *t2, LONG *hs)
#else
void HDIFTIM(t1, t2, hs)
YMDHMS_TIME_TYPE *t1, *t2;
LONG *hs;
#endif
{
   LONG m, hss;

   hss = to_hundredths_sec_s(t1->second) + (*hs);
   m = TIMMIN(t1) + div_pr(hss,6000);
   MINTIM(t2, &m);
   t2->second = mod_pr(hss,6000);
   t2->second |= 0x8000;
}

/*-----------------------------------------------------------------------------

   FUNCTION: TIMCMP

      It compares two YMDHMS_TIME_TYPE structures/arrays.

      Note:  no validity check is done on t1 and t2

   PARAMETERS:

      t1 = pointer to first time

      t2 = pointer to second time

   RETURNS:  0 if t1 = t2
             1 if t1 < t2
             2 if t2 < t1

   CALLED FROM:  C, FORTRAN

*/
#if PROTOTYPE_ALLOWED
LONG TIMCMP(YMDHMS_TIME_TYPE *t1, YMDHMS_TIME_TYPE *t2)
#else
LONG TIMCMP(t1, t2)
YMDHMS_TIME_TYPE *t1, *t2;
#endif
{
   SHORT t1_hs, t2_hs;

   if (t1->year < t2->year)     return(1);
   if (t1->year > t2->year)     return(2);
   if (t1->month < t2->month)   return(1);
   if (t1->month > t2->month)   return(2);
   if (t1->day < t2->day)       return(1);
   if (t1->day > t2->day)       return(2);
   if (t1->hour < t2->hour)     return(1);
   if (t1->hour > t2->hour)     return(2);
   if (t1->minute < t2->minute) return(1);
   if (t1->minute > t2->minute) return(2);
   t1_hs = to_hundredths_sec_s(t1->second);
   t2_hs = to_hundredths_sec_s(t2->second);
   if (t1_hs < t2_hs) return(1);
   if (t1_hs > t2_hs) return(2);
   return(0);
}

/*-----------------------------------------------------------------------------

   FUNCTION:  PCKTIM

      Given a YMDHMS_TIME_TYPE structure, it returns a packed block time.

   PARAMETER:

      t = pointer to YMDHMS_TIME_TYPE structure

   RETURNS:  packed block time

   CALLED FROM:  C, FORTRAN

*/
#if PROTOTYPE_ALLOWED
ULONG PCKTIM(YMDHMS_TIME_TYPE *t)
#else
ULONG PCKTIM(t)
YMDHMS_TIME_TYPE *t;
#endif
{
   return(t->minute + (BIT6 * t->hour)
      + (BIT11 * t->day)
      + (BIT16 * t->month)
      + (BIT20 * t->year));
}

/*-----------------------------------------------------------------------------

   FUNCTION:  UPCKTIM

      It unpacks a packed block time into a YMDHMS_TIME_TYPE structure.

   PARAMETERS:

      t = pointer to YMDHMS_TIME_TYPE structure

      block_time = pointer to packed block time

   RETURNS:  VOID

   CALLED FROM:  C, FORTRAN

*/
#if PROTOTYPE_ALLOWED
void UPCKTIM(YMDHMS_TIME_TYPE *t, ULONG *block_time)
#else
void UPCKTIM(t, block_time)
YMDHMS_TIME_TYPE *t;
ULONG *block_time;
#endif
{
   t->second = 0;
   t->minute = *block_time % BIT6;
   t->hour = (*block_time / BIT6) % BIT5;
   t->day = (*block_time / BIT11) % BIT5;
   t->month = (*block_time / BIT16) % BIT4;
   t->year = (*block_time / BIT20);
   return;
}

/*-----------------------------------------------------------------------------

   FUNCTION:  year_day

      It converts a YMDHMS_TIME_TYPE into a double-precision number of
      days from the beginning of a base year.

      NOTE: If this number is given with seven digits after the decimal,
      there is no loss of accuracy.  Therefore a double precision number
      is adequate for a period of many years from the base time.

   PARAMETERS:

      ymdhms_time = time to be converted
      year_base   = year to use

   RETURNS:  time in decimal days

*/
#if PROTOTYPE_ALLOWED
double year_day(YMDHMS_TIME_TYPE *time, int year_base)
#else
double year_day(time, year_base)
YMDHMS_TIME_TYPE *time;
int year_base;
#endif
#define hundredths_seconds_per_minute 6000.0
#define hundredths_seconds_per_day 8640000.0
{
   YMDHMS_TIME_TYPE base;

   base.year = year_base;
   base.month = base.day = 1;   /* January 1 */
   base.hour = base.minute = base.second = 0;
   return( ((LONG) (TIMMIN(time) - TIMMIN(&base))) * hundredths_seconds_per_minute
      + to_hundredths_sec_s(time->second)) / hundredths_seconds_per_day;
}

/*-----------------------------------------------------------------------------

   FUNCTION:  yd_to_ymdhmh_time

      It converts time expressed in decimal days to a YMDHMH_TIME_TYPE
      structure (using sign bit in seconds field to specify precision
      to hundredths of a second).

   PARAMETERS:

      yd = time to be converted (double-precision decimal days)
      year_base = year to use

      time_ptr = ptr to YMDHMS_TIME_TYPE result

   RETURNS:  void

*/
#if PROTOTYPE_ALLOWED
void yd_to_ymdhmh_time(double yd, int year_base, YMDHMS_TIME_TYPE *time_ptr)
#else
void yd_to_ymdhmh_time(yd, year_base, time_ptr)
double yd;
int year_base;
YMDHMS_TIME_TYPE *time_ptr;
#endif
{
   YMDHMS_TIME_TYPE base;
   long yd_min, y;

#define minutes_per_day 1440.0
#define seconds_per_day 86400.0

   base.year = year_base;
   base.month = base.day = 1;   /* January 1 */
   base.hour = base.minute = base.second = 0;
   yd_min = (long)(yd * minutes_per_day);
   y = yd_min + TIMMIN(&base);
   MINTIM(time_ptr, &y);
   time_ptr->second = ((yd * seconds_per_day - yd_min * 60.0) * 100.0);
   /* An automatic cast, hence truncation, is used above to avoid
   the problem of rounding 59.999 seconds up to 60.00.
   */
   time_ptr->second |= 0x8000;
   return;
}

/*-----------------------------------------------------------------------------

   FUNCTION:  yd_to_ymdhms_time

      It converts time expressed in decimal days to a YMDHMS_TIME_TYPE
      structure.

   PARAMETERS:

      yd = time to be converted (double-precision decimal days)
      year_base = year to use

      time_ptr = ptr to YMDHMS_TIME_TYPE result

   RETURNS:  void
*/


#if PROTOTYPE_ALLOWED
void yd_to_ymdhms_time(double yd, int year_base, YMDHMS_TIME_TYPE *time_ptr)
#else
void yd_to_ymdhms_time(yd, year_base, time_ptr)
double yd;
int year_base;
YMDHMS_TIME_TYPE *time_ptr;
#endif
{
   YMDHMS_TIME_TYPE base;
   double dsec;
   long sec;

   base.year = year_base;
   base.month = base.day = 1;   
   base.hour = base.minute = base.second = 0;
   dsec = yd * seconds_per_day;
   sec = (long) round(dsec);
   DIFTIM(&base, time_ptr, &sec);
   return;
}

#if PROTOTYPE_ALLOWED
int get_ymdhms_time(FILE *fp, YMDHMS_TIME_TYPE *t)
#else
int get_ymdhms_time(fp, t)
FILE *fp;
YMDHMS_TIME_TYPE *t;
#endif
{
   char buff[80];
   double hs = -1.0;  /* for user input in real seconds */
   double dd;
   int year;
   int d1, d2;

   t->hour = t->minute = t->second = 0;
   fgets(buff, 80, fp);
   if (sscanf(buff, " %hd %hd %hd %hd %hd %lf",
      &(t->year), &(t->month), &(t->day),
      &(t->hour), &(t->minute), &hs) < 3)
     {
       if (sscanf(buff, " %d.%d", &d1, &d2) == 2)    return(1); /* year is missing */
       if (sscanf(buff, " %d %lf", &year, &dd) != 2) return(1);
       else
         {
           year = yr4digit(year);
           yd_to_ymdhms_time(dd, year, t);
           return(0);
         }
     }

   t->year = yr4digit(t->year);
   t->second = set_hs(hs);
   return(0);
}

/* set_hs is a very special-purpose function: it takes a
POSITIVE double precision number of seconds, which must be
LESS THAN 59.995 (so that multiplied by 100 and rounded it
will be less than 6000), and returns a USHORT suitable for
the seconds field of a YMDHMS or YMDHMH structure.  There is
NO error checking.
*/

#if PROTOTYPE_ALLOWED
USHORT set_hs(double hs)
#else
USHORT set_hs(hs)
double hs;
#endif
{
   USHORT s, hun;

   hun = round(hs*100);      /* hundredths */
   s = round(hs);
   if (100*s != hun)
   {                 /* store as YMDHMH-type */
      s = hun; 
      s |= 0x8000;            /* set sign bit flag */
   }
   return(s);
}



