/* ***************************************  
 * *                                     * 
 * *   USEFUL TIME-RELATED SUBROUTINES   * 
 * *                                     * 
 * *************************************** 
 * File: libs/user/timesubs.c
 */     

/*
 *  COPYRIGHT (C) 1983, 1984, 1988, 1993, 1995, 1996, 1998, 2000, 2002,
 *                             2003  BY
 *          SIGMET INCORPORATED, WESTFORD MASSACHUSETTS, U.S.A.  
 * 
 * THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY BE USED AND  COPIED
 * ONLY  IN  ACCORDANCE WITH  THE  TERMS  OF  SUCH  LICENSE  AND WITH THE
 * INCLUSION OF THE ABOVE COPYRIGHT NOTICE.  THIS SOFTWARE  OR  ANY OTHER
 * COPIES  THEREOF MAY NOT BE PROVIDED OR OTHERWISE MADE AVAILABLE TO ANY
 * OTHER PERSON.  NO TITLE TO AND OWNERSHIP OF  THE  SOFTWARE  IS  HEREBY
 * TRANSFERED. 
 */    
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>

#define INCLUDE_SYSTIME_PROTOTYPES 1
#include "sigtypes.h"
#include "user_lib.h"

/* ======================================================================
 * Get relative binary time in milliseconds.  Two of these values may
 * be compared to get a time difference, but their absolute value is
 * without meaning.
 */
#define GRACETIME ((UINT4)(SINT4)-60000) /* Allowable backward time */

void getbtime( volatile UINT4 *ibtime_a )
{
  *ibtime_a = iGetBtimeTVal( NULL ) ;
}

UINT4 iGetBtimeTVal( volatile struct timeval *pTVal_a )
{
  UINT4 internal_time ; struct timeval tval ;
  static UINT4 iLastBtime ; static UINT2 lFirst = TRUE ;

  gettimeofday( &tval, NULL ) ; 

  internal_time = (UINT4)tval.tv_sec;
  internal_time *= 1000;
  internal_time += (UINT4)tval.tv_usec/1000;

  /* Fixup the binary time so that it is monotonic increasing, at
   * least within this process.  See comments below for btimeGrace(),
   * which we still need whenever binary times are compared across
   * processes.
   */
  if( ! lFirst ) {
    if( (internal_time - iLastBtime) > GRACETIME )
      internal_time = iLastBtime ;
  }
  iLastBtime = internal_time ; lFirst = FALSE ;

  /* Return the system's original TIMEVAL, but only if the caller was
   * really interested in it.
   */
  if( pTVal_a ) *pTVal_a = tval ;
  return( internal_time ) ;
}

/* The following routine should be used to fixup BTIME differences
 * prior to being used in comparisons.  The reason this routine is
 * necessary is that network time syncing may introduce momentary
 * reversals in time which cause UINT4 comparisons to bomb.  This
 * routine adds 60-seconds of grace period in the negative direction.
 */
UINT4 btimeGrace( UINT4 idiff_a )
{
  if( idiff_a > GRACETIME ) return( 0 ) ;
  else                      return( idiff_a ) ;
}

/* ======================================================================
 * Compare two YMDS time structures. Ignore milliseconds.
 * Entry Parameters
 *    YMDSA_A, YMDSB_A  : Two times
 * Exit Parameters
 *    RETURN            : -1 if YMDSA is less than YMDSB
 *                      :  0 if YMDSA is equal to YMDSB
 *                      : +1 if YMDSA is greater than YMDSB
 */
SINT4 iymds_compare
  (volatile const struct ymds_time *ymdsa_a,
   volatile const struct ymds_time *ymdsb_a
   )
{

  if ( ymdsa_a->iyear < ymdsb_a->iyear ) return -1;

  if ( ymdsa_a->iyear > ymdsb_a->iyear ) return  1;

  if ( ymdsa_a->imon  < ymdsb_a->imon )  return -1;

  if ( ymdsa_a->imon  > ymdsb_a->imon )  return  1;

  if ( ymdsa_a->iday  < ymdsb_a->iday )  return -1;

  if ( ymdsa_a->iday  > ymdsb_a->iday )  return  1;

  if ( ymdsa_a->isec  < ymdsb_a->isec )  return -1;

  if ( ymdsa_a->isec  > ymdsb_a->isec )  return  1;

  return 0;
}

/* ======================================================================
 * Compare two YMDS time structures, including milliseconds.
 * Entry Parameters
 *    YMDSA_A, YMDSB_A  : Two times
 * Exit Parameters
 *    RETURN            : -1 if YMDSA is less than YMDSB
 *                      :  0 if YMDSA is equal to YMDSB
 *                      : +1 if YMDSA is greater than YMDSB
 */
SINT4 iYmdsCompareMs
  (volatile const struct ymds_time *ymdsa_a,
   volatile const struct ymds_time *ymdsb_a
   )
{

  SINT4 iCompare;
  iCompare = iymds_compare( ymdsa_a, ymdsb_a );
  if (iCompare) return iCompare ;

  if ( MS_FROM_MILLS(ymdsa_a->imills) < MS_FROM_MILLS(ymdsb_a->imills) )  return -1;
  if ( MS_FROM_MILLS(ymdsa_a->imills) > MS_FROM_MILLS(ymdsb_a->imills) )  return 1;

  return 0;
}

/* ======================================================================
 * Copy serv_ymds structure from ymds version.
 */
void ServFromYmds
  (volatile struct serv_ymds_time *pYmdsOut_a,
   volatile const struct ymds_time *pYmdsIn_a
   )
{
  pYmdsOut_a->inanos = NANOS_FROM_MILLS( pYmdsIn_a->imills );
  pYmdsOut_a->isec   = pYmdsIn_a->isec;
  pYmdsOut_a->iday   = pYmdsIn_a->iday;
  pYmdsOut_a->imon   = pYmdsIn_a->imon;
  pYmdsOut_a->iyear  = pYmdsIn_a->iyear;
}

/* ======================================================================
 * Copy ymds structure from serv_ymds version.
 */
void YmdsFromServ
  (volatile struct ymds_time *pYmdsOut_a,
   volatile const struct serv_ymds_time *pYmdsIn_a
   )
{
  pYmdsOut_a->imills = MILLS_FROM_NANOS( pYmdsIn_a->inanos );
  pYmdsOut_a->isec   = pYmdsIn_a->isec;
  pYmdsOut_a->iday   = pYmdsIn_a->iday;
  pYmdsOut_a->imon   = pYmdsIn_a->imon;
  pYmdsOut_a->iyear  = pYmdsIn_a->iyear;
}

/* ======================================================================
 * Compare two serv YMDS time structures
 * Entry Parameters
 *    YMDSA_A, YMDSB_A  : Two times
 * Exit Parameters
 *    RETURN            : -1 if YMDSA is less than YMDSB
 *                      :  0 if YMDSA is equal to YMDSB
 *                      : +1 if YMDSA is greater than YMDSB
 */
SINT4 iymds_compare_serv
  (volatile const struct serv_ymds_time *ymdsa_a,
   volatile const struct serv_ymds_time *ymdsb_a
   )
{

  if ( ymdsa_a->iyear < ymdsb_a->iyear ) return -1;

  if ( ymdsa_a->iyear > ymdsb_a->iyear ) return  1;

  if ( ymdsa_a->imon  < ymdsb_a->imon )  return -1;

  if ( ymdsa_a->imon  > ymdsb_a->imon )  return  1;

  if ( ymdsa_a->iday  < ymdsb_a->iday )  return -1;

  if ( ymdsa_a->iday  > ymdsb_a->iday )  return  1;

  if ( ymdsa_a->isec  < ymdsb_a->isec )  return -1;

  if ( ymdsa_a->isec  > ymdsb_a->isec )  return  1;

  if ( NS_FROM_NANOS(ymdsa_a->inanos) < NS_FROM_NANOS(ymdsb_a->inanos) )  return -1;

  if ( NS_FROM_NANOS(ymdsa_a->inanos) > NS_FROM_NANOS(ymdsb_a->inanos) )  return  1;

  return 0;
}

/*===========================================
 * Pack hour/minute/second into seconds.
 */

SINT4 iPackTime
(SINT2 ihour_a,
 SINT2 iminute_a,
 SINT2 isecond_a
 )
{

  SINT4 iseconds;

  iseconds = isecond_a;
  iseconds += 60 * (SINT4)iminute_a;
  iseconds += 3600 * (SINT4)ihour_a;

  return iseconds;
}

/* ======================================================================
 * Add a number of seconds to a YMDS time structure.  Local times remain
 * local, UTC remains UTC.  Handles time zone info, for computer's zone
 * only.
 */
void ymds_add_seconds
  (SINT4 isec_a, 
   volatile struct ymds_time *ymds_a 
   )
{
  struct tm unpacked_time;
  time_t    packed_time;

  SINT4 ij;
  UINT2 idays, ihours, iminutes, iseconds, iInFlags;
      
  /* First add the seconds */
  ij = ((SINT4)ymds_a->isec) + isec_a;

  /* If the seconds are still within the same day, then we are done */
  if ( (ij>=0) &&( ij<86400) )
    {
      ymds_a->isec = ij;
      return;
    }

  unpacktime( ymds_a->isec,  &idays, &ihours, &iminutes, &iseconds );
      
  unpacked_time.tm_year  = ymds_a->iyear-1900;
  unpacked_time.tm_mon   = ymds_a->imon-1;
  unpacked_time.tm_mday  = ymds_a->iday;
  unpacked_time.tm_hour  = ihours;
  unpacked_time.tm_min   = iminutes;
  unpacked_time.tm_sec   = iseconds;
  /* Pull out the DST flag */
  iInFlags = FLAGS_FROM_MILLS(ymds_a->imills);
  if (YMDS_FLG_UTC&ymds_a->imills)
    {
      if (YMDS_FLG_LDST&ymds_a->imills)
	unpacked_time.tm_isdst = 1 ;
      else
	unpacked_time.tm_isdst = 0 ;
    }
  else
    {
      if (YMDS_FLG_DST&ymds_a->imills)
	unpacked_time.tm_isdst = 1 ;
      else
	unpacked_time.tm_isdst = 0 ;
    }
  
  packed_time = mktime( &unpacked_time );
  packed_time += isec_a;
  localtime_r( &packed_time, &unpacked_time );
  
  ymds_a->iyear = 1900 + unpacked_time.tm_year;
  ymds_a->imon  = 1 + unpacked_time.tm_mon;
  ymds_a->iday  = unpacked_time.tm_mday;
  ihours   = unpacked_time.tm_hour;
  iminutes = unpacked_time.tm_min;
  iseconds = unpacked_time.tm_sec;
  ymds_a->isec  = iPackTime( ihours, iminutes, iseconds );
  /* Put back in the DST flag */
  if (YMDS_FLG_UTC&ymds_a->imills)
    {
      if (unpacked_time.tm_isdst>0)
	ymds_a->imills|=YMDS_FLG_LDST;
      else
	ymds_a->imills&=~(YMDS_FLG_LDST);

      /* Patch time by an hour if the summer time flag changed. */
      if (ymds_a->imills&YMDS_FLG_LDST)
	{
	  if (!(iInFlags&YMDS_FLG_LDST))
	    ymds_add_seconds( -3600, ymds_a );
	}
      else
	{
	  if (iInFlags&YMDS_FLG_LDST)
	    ymds_add_seconds( 3600, ymds_a );
	}

    }
  else
    {
      if (unpacked_time.tm_isdst>0)
	ymds_a->imills|=YMDS_FLG_DST|YMDS_FLG_LDST;
      else
	ymds_a->imills&=~(YMDS_FLG_DST|YMDS_FLG_LDST);
    }
}
/* ======================================================================
 * Add a number of milliseconds to a YMDS time structure
 */
void YmdsAddMs
  (SINT4 iMillSec_a, 
   volatile struct ymds_time *ymds_a 
   )
{
  SINT4 iSeconds, iMilliSeconds, ij;
  UINT2 iFlags;

  /* Separate our integer seconds and fraction */
  iSeconds      = iMillSec_a/1000;
  iMilliSeconds = iMillSec_a - (1000*iSeconds);

  /* First add the milliseconds, and normalize into the seconds */
  iFlags = FLAGS_FROM_MILLS(ymds_a->imills);
  ij = MS_FROM_MILLS(ymds_a->imills) + iMilliSeconds;
  if (ij < 0)
    {
      ymds_a->imills = MILLS_FROM_MS_FLAGS( ij+1000, iFlags );
      iSeconds--;
    }
  else if (ij >= 1000)
    {
      ymds_a->imills = MILLS_FROM_MS_FLAGS( ij-1000, iFlags );
      iSeconds++;
    }
  else
    ymds_a->imills   = MILLS_FROM_MS_FLAGS( ij     , iFlags );

  /* Now add the seconds */
  ymds_add_seconds( iSeconds, ymds_a );
      
}

/* ======================================================================
 * Return current time as a YMDS structure
 */
void GetTimeYmds
  (volatile struct ymds_time *pYmds_a,
   int iFlags_a )
{
  struct full_time_spec Time;

  GetTimeFull( &Time, iFlags_a );
  *pYmds_a = Time.Ymds;
}
/* ======================================================================
 * Return current time as a full_time_spec structure
 */
void GetTimeFull
  (volatile struct full_time_spec *pFullTime_a,
   int iFlags_a )
{
  struct timeval tval ;

  gettimeofday( &tval, NULL ) ;

  TimeFullFromTimeval( pFullTime_a, &tval, iFlags_a );

}
/* ======================================================================
 * Return current time as a full_time_spec structure
 */
void TimeFullFromTimeval
  (volatile struct full_time_spec *pFullTime_a,
   const struct timeval *pTV_a,
   int iFlags_a )
{
  struct tm RecordedTm, LocalTm ;
  SINT2 ihour, iminute, isecond ;
  SINT4 iRelDay;

  memset( (struct full_time_spec *)pFullTime_a, 0, sizeof(*pFullTime_a) );

  pFullTime_a->tv_sec  = pTV_a->tv_sec;
  pFullTime_a->tv_usec = pTV_a->tv_usec;

  localtime_r( &pTV_a->tv_sec, &LocalTm ) ;
  if ( LocalTm.tm_isdst>0 )
    {
      strncpy( (char *)pFullTime_a->sLocalTZName, tzname[1], TZNAME_SIZE-1 );
      pFullTime_a->Ymds.imills |= YMDS_FLG_LDST;
    }
  else
    strncpy( (char *)pFullTime_a->sLocalTZName, tzname[0], TZNAME_SIZE-1 );
  pFullTime_a->iLocalWest = timezone/60;
  if (iFlags_a==TIME_FLG_LOCAL)
    {
      RecordedTm = LocalTm;
      pFullTime_a->iMinutesWest = pFullTime_a->iLocalWest;
      strncpy( (char *)pFullTime_a->sRecordTZName, (char *)pFullTime_a->sLocalTZName,
	       TZNAME_SIZE-1 );
      if ( RecordedTm.tm_isdst>0 ) pFullTime_a->Ymds.imills |= YMDS_FLG_DST;
    }
  else
    {
      gmtime_r( &pTV_a->tv_sec, &RecordedTm ) ;
      pFullTime_a->iMinutesWest = 0;
      strcat( (char *)pFullTime_a->sRecordTZName, "UTC" );
      pFullTime_a->Ymds.imills |= YMDS_FLG_UTC;
    }

  pFullTime_a->Ymds.iyear = 1900 + RecordedTm.tm_year;
  pFullTime_a->Ymds.imon  = 1 + RecordedTm.tm_mon;
  pFullTime_a->Ymds.iday  = RecordedTm.tm_mday;
  pFullTime_a->iJulianDay = RecordedTm.tm_yday;
  
  ihour   = RecordedTm.tm_hour;
  iminute = RecordedTm.tm_min;
  isecond = RecordedTm.tm_sec;
  pFullTime_a->Ymds.isec = iPackTime( ihour, iminute, isecond );
  pFullTime_a->Ymds.imills |= YMDS_MASK_MS & (pTV_a->tv_usec/1000);

  iRelDay = ( 365 * RecordedTm.tm_year ) + RecordedTm.tm_yday ;
  iRelDay += ((RecordedTm.tm_year -   1) /   4 ) ; /* Account for leap years */
  iRelDay -= ((RecordedTm.tm_year -   1) / 100 ) ; /* Account for leap century */
  iRelDay += ((RecordedTm.tm_year + 299) / 400 ) ; /*  except every 400 years. */

  pFullTime_a->iRelDay = iRelDay - 25550;      /* Since 1970, just to make it smaller */
}
/* ======================================================================
 * Return convert UNIX time_t value to ymds_time structure.  
 * 
 */
void YmdsFromTime_t
  (struct ymds_time *pYmds_a,
   time_t iTime_a,
   UINT2 iMilliseconds_a,
   int iFlags_a )
{
  struct tm Time;
  memset( pYmds_a, 0, sizeof(*pYmds_a) );

  if (iFlags_a==TIME_FLG_LOCAL)
    {
      localtime_r( &iTime_a, &Time ) ;
      if ( Time.tm_isdst>0 )
	pYmds_a->imills = MILLS_FROM_MS_FLAGS( iMilliseconds_a, YMDS_FLG_DST|YMDS_FLG_LDST );
      else
	pYmds_a->imills = MILLS_FROM_MS_FLAGS( iMilliseconds_a, 0 );
    }
  else
    {
      gmtime_r( &iTime_a, &Time ) ;
      pYmds_a->imills = MILLS_FROM_MS_FLAGS( iMilliseconds_a, YMDS_FLG_UTC );
    }
  pYmds_a->iyear  = Time.tm_year + 1900 ;
  pYmds_a->imon   = Time.tm_mon  + 1    ;
  pYmds_a->iday   = Time.tm_mday        ;
  pYmds_a->isec   = Time.tm_sec + 60*( Time.tm_min + 60*Time.tm_hour ) ;

}

/* ======================================================================
 * Get the time_t data structure from the ymds time.  Works for
 * UTC time or time of the local computer's timezone.
 */
time_t time_tFromYmds
(volatile const struct ymds_time *pYmds_a,
 MESSAGE *iStatus_a )
{
  struct ymds_time LocalYmds;
  struct tm unpacked_time;  time_t iTime;
  UINT2 iday, ihour, iminute, isec;

  *iStatus_a = SS_NORMAL;

  LocalYmds = *pYmds_a;
  if (UTC_FROM_MILLS(LocalYmds.imills))
    {
      tzset();
      ymds_add_seconds( -timezone, &LocalYmds );
    }
    
  unpacktime( LocalYmds.isec, &iday, &ihour, &iminute, &isec ) ;
  
  unpacked_time.tm_year  = LocalYmds.iyear-1900 ;
  unpacked_time.tm_mon   = LocalYmds.imon-1 ;
  unpacked_time.tm_mday  = LocalYmds.iday ;
  unpacked_time.tm_hour  = ihour ;
  unpacked_time.tm_min   = iminute ;
  unpacked_time.tm_sec   = isec ;
  if (YMDS_FLG_DST&pYmds_a->imills)
    unpacked_time.tm_isdst = 1 ;
  else
    unpacked_time.tm_isdst = 0 ;

  iTime = mktime( &unpacked_time );
  if( iTime == (time_t)-1 ) 
    *iStatus_a = errno_to_message_va( errno, 1, "time_tFromYmds()" ) ;
  
  return iTime;
}
/* ======================================================================
 * Set the current (local) system time to a YMDS structure
 */
MESSAGE SetTimeYmds
(volatile const struct ymds_time *pYmds_a )
{
  MESSAGE istatus; 
  struct timeval tval;

  tval.tv_sec = time_tFromYmds( pYmds_a, &istatus );
  tval.tv_usec = 1000 * MS_FROM_MILLS( pYmds_a->imills);
  if( istatus == SS_NORMAL )
    {
      if( settimeofday( &tval, NULL ) ) 
	{
	  istatus = errno_to_message_va( errno, 1, "SetTimeYmds()" ) ;
	  
	  /* If the stime() call failed, then try again with root privileges.
	   * However, do not try to recover from any further failures after this
	   * point.
	   */
	  if( sig_run_as_root() == SS_NORMAL ) {
	    if( settimeofday( &tval, NULL ) ) {
	      istatus = errno_to_message_va( errno, 1, "SetTimeYmds()" ) ;
	    } else {
	      istatus = SS_NORMAL ;
	    }
	    if( sig_run_as_operator() != SS_NORMAL )
	      if( istatus == SS_NORMAL )
		istatus = errno_to_message_va( errno, 1, "SetTimeYmds()" ) ;
	  }
	  
	} else {
	  istatus = SS_NORMAL;
	}
    }
  
  return istatus;
}

/* ======================================================================
 * Subtract two YMDS time structures, give the number of seconds difference.
 * This difference is not guarenteed to be accurate if the difference is
 * greater than 1 year, but it will peg out at a 1 year (366 days) difference 
 * with the correct sign.  It ignores the fractional seconds in the
 * input args.
 *
 * Entry Parameters
 *    YMDSA_A, YMDSB_A  : Two times
 * Exit Parameters
 *    RETURN            : YMDSA minus YMDSB in seconds
 */
SINT4 iymds_subtract
  (volatile const struct ymds_time *ymdsa_a,
   volatile const struct ymds_time *ymdsb_a
   )
{

  struct tm unpacked_time;
  time_t    timea, timeb;
  UINT2 iday, ihour, iminute, isec;
  SINT4 isec_difference, iyear_difference;
  /* ---------------------------------------------------- */

  isec_difference = (SINT4)(ymdsa_a->isec - ymdsb_a->isec);

  /* If the year, month, and day are the same, then the difference between
   * the seconds is sufficient
   */

  if( ymdsa_a->iyear == ymdsb_a->iyear )
    {
      if( ymdsa_a->imon == ymdsb_a->imon )
	{
	  if( ymdsa_a->iday == ymdsb_a->iday ) return isec_difference;
	}
    }

  /* We max out at one year.  
   * First check the difference between the two years, if it is 3 or more, then
   * peg out with the maximum difference and correct sign.
   */
  iyear_difference = (SINT4)(ymdsa_a->iyear - ymdsb_a->iyear);

  if (iyear_difference <= -3) return -MAX_SUBTRACT_SECONDS;

  if (iyear_difference >= 3) return MAX_SUBTRACT_SECONDS;

  /*
   * Difference is less than 3 years, so we have to do a full subtract, and 
   * can know that the result will fit in a 32-bit seconds value.  Note that
   * we have to figure out which is larger, then shuffle the subtract. 
   */
  unpacked_time.tm_year = ymdsa_a->iyear-1900;
  unpacked_time.tm_mon  = ymdsa_a->imon-1;
  unpacked_time.tm_mday = ymdsa_a->iday;
  unpacktime( ymdsa_a->isec, &iday, &ihour, &iminute, &isec );
  unpacked_time.tm_hour  = ihour;
  unpacked_time.tm_min   = iminute;
  unpacked_time.tm_sec   = isec;
  if (YMDS_FLG_DST&ymdsa_a->imills)
    unpacked_time.tm_isdst = 1 ;
  else
    unpacked_time.tm_isdst = 0 ;  

  timea = mktime( &unpacked_time );

  unpacked_time.tm_year = ymdsb_a->iyear-1900;
  unpacked_time.tm_mon  = ymdsb_a->imon-1;
  unpacked_time.tm_mday = ymdsb_a->iday;
  unpacktime( ymdsb_a->isec, &iday, &ihour, &iminute, &isec );
  unpacked_time.tm_hour  = ihour;
  unpacked_time.tm_min   = iminute;
  unpacked_time.tm_sec   = isec;
  if (YMDS_FLG_DST&ymdsb_a->imills)
    unpacked_time.tm_isdst = 1 ;
  else
    unpacked_time.tm_isdst = 0 ;

  timeb = mktime( &unpacked_time );

  isec_difference = timea - timeb;

  isec_difference = MIN( isec_difference, MAX_SUBTRACT_SECONDS );
  isec_difference = MAX( isec_difference, -MAX_SUBTRACT_SECONDS );
  
  return isec_difference;
}

/* ======================================================================
 * Subtract two YMDS time structures, give the number of milliseconds difference.
 * This difference is not guarenteed to be accurate if the difference is
 * greater than 21 days, but it will peg out at a 21 days difference 
 * with the correct sign.
 *
 * Entry Parameters
 *    YMDSA_A, YMDSB_A  : Two times
 * Exit Parameters
 *    RETURN            : YMDSA minus YMDSB in milliseconds.
 */
SINT4 iYmdsSubtractMs
  (volatile const struct ymds_time *ymdsa_a,
   volatile const struct ymds_time *ymdsb_a
   )
{
  SINT4 isec_difference, ims_difference;
  /* ---------------------------------------------------- */

  isec_difference = iymds_subtract( ymdsa_a, ymdsb_a );
  ims_difference = MS_FROM_MILLS(ymdsa_a->imills) - MS_FROM_MILLS(ymdsb_a->imills);
  /* Force the signs to match: */
  if ((isec_difference>0)&&(ims_difference<0))
    {
      isec_difference--;
      ims_difference+=1000;
    }
  if ((isec_difference<0)&&(ims_difference>0))
    {
      isec_difference++;
      ims_difference-=1000;
    }

  /* We max out at 21 days */

  if (isec_difference>= (MAX_SUBTRACT_MILLISECONDS/1000)) return  MAX_SUBTRACT_MILLISECONDS;
  if (isec_difference<=-(MAX_SUBTRACT_MILLISECONDS/1000)) return -MAX_SUBTRACT_MILLISECONDS;

  return (1000*isec_difference)+ims_difference;
}

/* ==================================================
 * Converts seconds into days, hours, minutes, and seconds
 */
void unpacktime
  (UINT4 ipackedseconds_a,           /* Seconds since midnight, positive */
   volatile UINT2 *iday_a,           /* Number of elapsed days */
   volatile UINT2 *ihour_a,          /* the hour (0-23) */
   volatile UINT2 *iminute_a,        /* The minute */
   volatile UINT2 *isecond_a )       /* The second */
{
  UINT4 itemp;

  itemp = ipackedseconds_a;

  *isecond_a = itemp % 60;
  itemp /= 60;

  *iminute_a = itemp % 60;
  itemp /= 60;

  *ihour_a = itemp % 24;
  itemp /= 24;

  *iday_a = itemp;
}

/* ======================================================================
 * Convert a year value in the range 0-99 (used in IRIS filenames) to a full 
 * year value in the range 1950-2049.
 *
 * Entry Parameters
 *    iyear : Two-digit year
 * */
SINT4 iYearFromFile(SINT4 iyear)
{
  SINT4 ifile_year_v;
  
  if( iyear < 0 || iyear > 100 )
    {
      ifile_year_v = 0;	/* Error Return */
    }
  else if( iyear < 50 )
    {
      ifile_year_v = 2000 + iyear;
    }
  else
    {
      ifile_year_v = 1900 + iyear;
    }
  
  return( ifile_year_v );
}
/* ==================================================================
 * Given the time in a SIGMET ymds structure, convert it to UTC.
 * It may already be so.
 */
void UtcFromYmds
(struct ymds_time *pYmdsUtcOut_a,
 const struct ymds_time *pYmdsIn_a,
 SINT4 iMinutesWest_a )          /* Time offset to standard time */
{
  *pYmdsUtcOut_a = *pYmdsIn_a;

  if (!(YMDS_FLG_UTC&pYmdsIn_a->imills))
    {
      SINT4 iMinutesWest;
      /* Only add offset if we recorded local time. */
      iMinutesWest = iMinutesWest_a;
      /* Time is actually 1 hour east if we recorded summer time */
      if (YMDS_FLG_DST&pYmdsIn_a->imills)
	{ iMinutesWest -= 60; pYmdsUtcOut_a->imills &= ~YMDS_FLG_DST; }
      ymds_add_seconds( 60 * iMinutesWest, pYmdsUtcOut_a );
      pYmdsUtcOut_a->imills |= YMDS_FLG_UTC;
    }	  
}
