/************************************************************************/
/* SISCO SOFTWARE MODULE HEADER *****************************************/
/************************************************************************/
/*   (c) Copyright Systems Integration Specialists Company, Inc.,	*/
/*		1994 - 2008, All Rights Reserved			*/
/*									*/
/* MODULE NAME : time_str.c						*/
/* PRODUCT(S)  : 							*/
/*									*/
/* MODULE DESCRIPTION : 				  		*/
/*									*/
/* GLOBAL FUNCTIONS DEFINED IN THIS MODULE :				*/
/*									*/
/* MODIFICATION LOG :							*/
/*  Date     Who   Rev			Comments			*/
/* --------  ---  ------   -------------------------------------------	*/
/* 06/24/08  EJV     41    Btime6StringToVals CORR: *1000 was missing.	*/
/* 02/28/08  JRB     40    GetTimeAndUsec ONLY for _WIN32, linux, QNX.	*/
/* 09/20/07  nav     39    Put TimeAccuracy in UTC Quality string       */
/* 02/15/07  RLH     38    Add validity checks to UtcValueToXmlString   */
/*                         so that invalid data will not cause a bad    */
/*                         string to be generated.                      */
/* 01/16/07  RLH     37    Comment-out code obsoleted by rev 36         */
/* 11/29/06  RLH     36    Merge time_str.c and time_str2, adjust revs, */
/*                         add portable usr_mkgmtime function,          */
/*                         rewrite date and time parsing functions      */ 
/*                         tstrStringToTime, tstrStringToTm;            */
/*                         remove stdtime.h include, add validation to  */
/*                         quality-bit string, make date-field ordering */
/*                         (m-d-y, d-m-y, y-m-d) based on locale, not   */
/*                         always assuming US m-d-y ordering. improve   */
/*                         compliance with use of standard SISCO types. */
/* 09/13/06  NAV     35    Fix quality bits in UTC conversion           */
/* 07/13/06  MDE     34    Fixed compile warning			*/
/* 06/05/06  RLH     33    Change to stdtime include                    */
/* 03/06/06  RLH     32    add GetTimeAndUsec, tstrTimeToStringGmt,     */
/*                         tstrStringToTimeGmt                          */
/* 02/28/06  EJV     31    UtcStringToVals: added cast.			*/
/* 01/30/06  GLB     30    Integrated porting changes for VMS           */
/* 02/13/06  DSF     29    Migrate to VS.NET 2005			*/
/* 02/03/06  NAV     28    UTC - use 0x1000000 instead of 0xFFFFFF	*/
/* 01/30/06  GLB     27    Integrated porting changes for VMS           */
/* 07/27/04  DWL     26    Added tstrTmToString (struct tm)		*/
/* 07/16/04  DWL     25    Added tstrStringToTm (struct tm)		*/
/* 01/27/04  nav     24    UtcValsToString: check for gmtime failure	*/
/* 12/09/03  JRB     24    Btime6String..: stop using "daylight" global,*/
/*			   use tm_isdst=0 to treat input as std time.   */
/* 10/30/03  GLB &   23    Reworked "XmlStringToUtcValue" & 		*/
/*           EJV               "UtcValueToXmlString"                    */
/* 10/24/03  DSF     22    Fixed SX_DATE_TIME functions			*/
/* 10/15/03  JRB     21    Del _WIN32 ifdef.				*/
/* 09/01/03  GLB     20    Added "XmlStringToUtcValue" & 		*/
/*                          "UtcValueToXmlString"                       */
/* 06/03/03  DSF     19    More compiler warnings			*/
/* 04/14/03  JRB     18    Eliminate compiler warnings.			*/
/* 09/26/02  NAV     17    UtcValsToString: use gmtime                  */
/* 08/05/02  NAV     16    Make Btime4 conversion funcs ansi compatible */
/* 07/12/02  NAV     15    Add UtcValsToString and UtcStringToVals      */
/* 06/07/02  NAV     14    sprintf msec to %03d                         */
/* 02/25/02  JRB     13    Don't use "daylight" global on VXWORKS.	*/
/*			   Fix "thisFil..", sprintf call. Add str_util.h*/
/* 12/27/01  GLB     12    Remove embedded comment in prior log 11      */
/* 12/19/01  EJV     11    Converted comments from double slash to      */
/*                          slash star                                  */
/*			   Substituted AXS4_LOG_xxx with SLOGALWAYS.	*/
/*			   Replaced sys includes with sysincs.h		*/
/* 06/26/01  EJV     10    Eliminated globals to make thread safe.	*/
/* 02/02/01  EJV     09    tstrStringToTime: chg (long *) to (time_t *) */ 
/* 11/21/00  MDE     08    Changes for QNX (Btime4 for Win32 only)	*/
/* 07/17/98  NAV     07    Modify century calculation for y2k		*/
/* 10/15/97  NAV     06    Add Btime4 Support Functions			*/
/* 10/08/97  NAV     05    Add seconds to Btime4 and Gtime		*/
/* 10/06/97  NAV     04    Handle daylight savings time problem - Btime6*/
/* 09/04/97  NAV     03    Add Btime6 Conversion routines		*/
/* 08/01/96  NAV     02    Lint CleanUp					*/
/* 11/08/94  MDE     01    New						*/
/************************************************************************/

#if defined (_WIN32)
#pragma warning(disable : 4996)
#endif

#include "glbtypes.h"
#include "sysincs.h"
#include "slog.h"
#include "time_str.h"
#include "str_util.h"

#ifdef DEBUG_SISCO
SD_CONST static ST_CHAR *SD_CONST thisFileName = __FILE__;
#endif

/************************************************************************/

ST_CHAR *tstrTimeFormat  = TSTR_DEF_TIME_FORMAT;
ST_CHAR *BtimeTimeFormat = BTIME_DEF_TIME_FORMAT;

/************************************************************************/
/************************************************************************/
/*                       tstrTimeToString  				*/
/************************************************************************/

ST_RET tstrTimeToString (time_t t, ST_CHAR *dest)
  {
  strftime (dest, MAX_TIME_STRING_LEN, tstrTimeFormat, localtime (&t));
  return (SD_SUCCESS);
  }


/************************************************************************/
/*                       tstrTimeToStringGmt				*/
/*  this code was merged from time_str2.c                               */
/************************************************************************/

ST_RET tstrTimeToStringGmt (time_t t, ST_CHAR *dest)
  {
  strftime (dest, MAX_TIME_STRING_LEN, tstrTimeFormat, gmtime (&t));
  return (SD_SUCCESS);
  }


/************************************************************************/
/************************************************************************/

/* structure to hold results from parsing date and time strings  */

typedef struct
  {
  int                       month;
  int                       day;
  int                       year;
  int                       hour;
  int                       min;
  int                       sec;
  int                       mSec;
  int                       uSec;
  int                       nSec;
  int                       zoneHour; 
  int                       zoneMin;
  char                      zoneCode;     /* 'Z', '+', '-' or 0 */
  int                       dateFound;    /* not needed by new parsers */
  int                       timeFound;    /* not needed by new parsers */
  int                       order;        /*date-field ordering; 0 =f ANY */
  char                      *pflags;      /* ptr to (qual= ... ) or NULL */

  } _TS_DATETIME;

#if 0
/* obsoleted code */
static int parseDateString (char *s, _TS_DATETIME *dt);
static int parseTimeString (char *s, _TS_DATETIME *dt);
static int parseBtimeString (char *s, _TS_DATETIME *dt);

static int strToMonth (char *s, int *monthOut);
#endif

/************************************************************************/


typedef struct
  {
  ST_INT32                  /*O*/ len;        /* length of field */
  ST_INT32                  /*O*/ value;      /* extracted num value */ 
  ST_INT32                  /*O*/ scale;      /* for scaling fractions */
  ST_CHAR                   /*O*/ delim;      /* field delimiter */
  ST_CHAR                   /*O*/ text[16];   /* extracted text value */
  } DATETIME_FIELD;


/************************************************************************/
/*                       getDateTimeField				*/
/*  extract a (possible) date or time field, and report its value       */
/*  return pointer to next position to parse, or NULL if no field found */
/*  a _DATETIME_FIELD len of 0 also means the field is not valid        */
/************************************************************************/

static ST_CHAR *            getDateTimeField (
  ST_CHAR *                 /*I*/ buf,        /* field being parsed */
  ST_CHAR                   /*I*/ prevDelim,  /* '.' allows longer num */
  DATETIME_FIELD *          /*O*/ dtf)
  {
  ST_INT32                  i;
  ST_CHAR                   work[4];

  static ST_CHAR *          monthTab[13] =
    {
    "",
    "JAN", "FEB", "MAR",
    "APR", "MAY", "JUN",
    "JUL", "AUG", "SEP",
    "OCT", "NOV", "DEC"
    };


  if (dtf == NULL)
    {
    return NULL;    /* bad parameter */
    }

  dtf->len   = 0;
  dtf->value = 0;
  dtf->scale = 1;
  dtf->delim = ' ';
  dtf->text[0] = 0;

  if (buf == NULL)
    {
    return NULL;    /* bad buf pointer */
    }

  /* skip leading whitespace */

  while (isspace (*buf))
    {
    buf++;
    }

  if (*buf == 0)
    {
    return NULL;    /* buf has no data */
    }

  /* accumulate value */

  while (isdigit (*buf))
    {
    dtf->text[dtf->len] = *buf;
    dtf->text[dtf->len+1] = 0;

    dtf->value = (dtf->value * 10) + (*buf - '0');
    dtf->scale *= 10;
    dtf->len++;
    buf++;

    if (dtf->len > 9)
      {
      return NULL;    /* numeric field too long */
      }
    } /* while */

  if (dtf->len > 0)    /* field is numeric */
    {

    /* only lengths 1, 2 and 4 are allowed */
    /* this allows for 1 or 2 digit months, days and years */
    /* and 4 digit years */

    /* when prevDelim is '.', the current field is a fraction */
    /* so, we allow longer field values */

    if (prevDelim == '.')
      {
      if (dtf->len == 9)
        {
        /* a (fake) length of 9 is a 'marker' for an alpha month */
        /* we change numbers of length 9 to 8 to get around this */

        /* we are lying about the length; don't worry, it's OK */

        /* the precise length of fractional seconds isn't critical anyway. */
        /* we need lengths mainly to analyze month/day/year ordering. */

        dtf->len = 8;
        }

      else if (dtf->len > 9)
        {
        /* too many digits for a fraction */
        return NULL;
        }
      }

    else /* prevDelim != '.' */
      {
      /* numbers for dates and times only have valid lengths of 1, 2 or 4 */
      /* except for fractional seconds, which can vary from 0 to 9 digits */

      if ( (dtf->len > 4)
      ||   (dtf->len == 3) )
        {
        /* not a valid date or time numeric field size */
        return NULL;
        }

      /* if a number is followed by . then a blank, nul, +/-, T or Z */
      /* the . is just noise, so ignore it */

      /* this test is not made on the "if prevDelim == '.'" branch above */
      /* we don't want to permit values like .123. which is illegal */

      if (*buf == '.')
        {
        if ( (buf[1] ==  0 )
        ||   (buf[1] == ' ')
        ||   (buf[1] == '+')
        ||   (buf[1] == '-')
        ||   (buf[1] == 'T')
        ||   (buf[1] == 'Z') )
          {
          buf++;    /* a pointless dot */
          }
        } /* *buf == '.' */

      } /* when prevDelim != '.' */

    /* next field is after curr delim at 'buf'; assume delim len is 1 */
    /* assume curr pos is the delimiter */
    /* example: buf == "-12", so delim == '-' and next field "12" at buf+1 */
    /* this holds in most cases, except AM/PM, when next field at buf+2 */

    dtf->delim = (ST_CHAR) toupper (*buf);

    if (*buf == 0)    /* end of buffer */
      {
      /* delim is blank when there is no non-blank delimiter */
      /* to determine if end of buffer, look at dtf->buf */
      /* we don't return buf+1 because end of buffer was reached */
      /* be careful not to run off end of buffer */
      dtf->delim = ' ';
      return buf;
      }

    if (isspace (*buf))
      {
      /* delim is blank when there is no non-blank delimiter */
      /* to determine if end of buffer, look at dtf->buf */
      dtf->delim = ' ';     /* just in case of tabs, etc. */
      return buf + 1;
      }

    if ( (*buf == ':')      /* part of a time */
    ||   (*buf == '.')      /* part of a time */
    ||   (*buf == '-')      /* part of a date */
    ||   (*buf == '+') )    /* possibly part of a timezone offset */
      {
      return buf + 1;
      }

    if (*buf == '/')
      {
      dtf->delim = '-';     /* treat '/' same as '-' for dates */
      return buf + 1;
      }

    if ( (dtf->delim == 'T')
    &&   (isdigit (buf[1])) )    /* value is part of ISO 8601 time */
      {
      return buf + 1;
      }

    if (dtf->delim == 'Z')
      {
      if ( (buf[1] <= ' ')       /* Zulu/GMT timezone suffix */
      ||   (buf[1] == '(') )     /* Z followed by (qual= ... ) */
        {
        return buf + 1;
        }
      } /* Z */

    /* look for A, P, AM or PM suffix */

    if ( (dtf->delim == 'A')
    ||   (dtf->delim == 'P') )
      {
      if (!isalnum (buf[1]))          /* short AM/PM suffix */
        {
        return buf + 1;
        }

      if ( (toupper (buf[1]) == 'M')  /* long AM/PM suffix */
      &&   (!isalnum (buf[2])) )      /* good delim after M */
        {
        return buf + 2;
        }

      return NULL;                    /* malformed AM/PM */

      } /* A or P delim */

    } /* field is numeric */

  /* look for alphabetic month */
  /* we are supporting the following date formats: */

  /* 2006-Dec-17 */
  /*      Dec-17-06 */
  /*      Dec-17-2006 */
  /*      17-Dec-2006 */

  /* in each case, the alpha month is followed by '-' or '/' */

  /* the format 06-Dec-17 is not supported, because it is not */
  /* clear if it is day-month-year or year-month-day order. */

  /* if found, return equivalent value (1 to 12) and a (fake) length of 9 */
  /* check for length of 9 is used to see if an alpha month is present */

  if ( (buf[3] != '-')
  &&   (buf[3] != '/') )
    {
    return NULL;
    }

  work[0] = (ST_CHAR) toupper (buf[0]);
  work[1] = (ST_CHAR) toupper (buf[1]);
  work[2] = (ST_CHAR) toupper (buf[2]);
  work[3] = 0;

  strcpy (dtf->text, work);

  for (i=1; i <= 12; i++)
    {
    if (strcmp (work, monthTab[i]) == 0)
      {
      dtf->delim = '-';     /* treat '/' same as '-' for dates */
      dtf->value = i;
      dtf->len = 9;         /* fake length of 9 = alpha month */
      return buf + 4;
      }
    }

  return NULL;    /* no valid date/time field found */

  } /* getDateTimeField */


/************************************************************************/
/*                       getDateOrder                                   */
/*                                                                      */
/* on Windows systems, use GetLocaleInfo to determine the ordering of   */
/* month, day and year fields in a date, according to the current       */
/* locale.  on non-Windows systems, assume m/d/y.                       */
/*                                                                      */
/************************************************************************/

#ifdef _WIN32
/* on Windows, GetLocaleInfo() depends on windows.h and kernel32.lib */
#pragma comment(lib, "kernel32.lib")
#include <windows.h>
#endif

ST_INT32                         getDateOrder ()
  {
#ifdef _WIN32
  ST_CHAR                      work[2];
  ST_INT32                  rc;

  work[0] = work[1] = 0;
  rc = GetLocaleInfoA (LOCALE_USER_DEFAULT, LOCALE_ILDATE, work, 2); 

  if (rc != 2)
    {
    return S_DATE_ORDER_MDY;  
    }

  if (work[0] == '1') 
    {
    return S_DATE_ORDER_DMY;  
    }

  if (work[0] == '2') 
    {
    return S_DATE_ORDER_YMD;  
    }

  /* in case of error, or default '0' returned */
  return S_DATE_ORDER_MDY;  

#else
  /* unable to determine ordering, so assume a default order */
  return S_DATE_ORDER_MDY;  
#endif
  }


/************************************************************************/
/*                       storeDateValue                                 */
/*                                                                      */
/* take a DATETIME_FIELD array, determine field order of a date, and    */
/* store into a _TS_DATETIME.  if DATETIME_FIELD array is valid, and    */
/* the fields appear to be in the correct order, return SD_SUCCESS,     */
/* else return SD_FAILURE.                                              */
/*                                                                      */
/************************************************************************/

ST_RET                      storeDateValue (
  DATETIME_FIELD *          /*I*/  dtf, 
  _TS_DATETIME *            /*IO*/ dt)
  {
  ST_INT32                  len;
  ST_INT32                  mm;
  ST_INT32                  dd;
  ST_INT32                  yy;
  ST_INT32                  order;

  if (dt == NULL)
    {
    return SD_FAILURE;
    }

  dt->month = 0;
  dt->day   = 0;
  dt->year  = 0;
  dt->dateFound = SD_FALSE;

  if (dtf == NULL)
    {
    return SD_FAILURE;
    }

  if ( (dtf[0].delim != '-')
  ||   (dtf[1].delim != '-') )
    {
    return SD_FAILURE;
    }

  if ( (dtf[2].delim != 'T')
  &&   (dtf[2].delim != ' ') )
    {
    return SD_FAILURE;
    }

  /* determine field order by the lengths of 3 fields */
  /* form a single int value for easier testing */

  /* for example, 412 means a 4-digit field, then a 1-digit field */
  /* and finally a 2-digit field, in that order. */

  len = (100 * dtf[0].len) + (10 * dtf[1].len) + (dtf[2].len);

  /* if a specific date order is requested by caller, use it. */
  /* otherwise, ask system for the default order */
  
  if (dt->order == S_DATE_ORDER_ANY)
    {
    /* caller accepts any currently active order, based on locale */
    order = getDateOrder ();
    }
  
  else
    {
    /* caller insisted on date fields being in a particular order */
    order = dt->order;
    }

  switch (len)
    {
    case 411:   /* yyyy-m-d    : ordering is confident */
    case 412:   /* yyyy-m-dd   : ordering is confident */
    case 421:   /* yyyy-mm-d   : ordering is confident */
    case 422:   /* yyyy-mm-dd  : ordering is confident */
    case 491:   /* yyyy-Mon-d  : ordering is confident */
    case 492:   /* yyyy-Mon-dd : ordering is confident */

      /* when date starts with a 4-digit number, the first field is a year. */
      /* no (known) locales use year-day-month, so we can assume the order */
      /* is year-month-day here, regardless of locale, with confidence. */

      yy = dtf[0].value;
      mm = dtf[1].value;
      dd = dtf[2].value;
      break;

    case 914:   /* Mon-d-yyyy  : ordering is certain */
    case 924:   /* Mon-dd-yyyy : ordering is certain */

      mm = dtf[0].value;
      dd = dtf[1].value;
      yy = dtf[2].value;
      break;

    case 114:   /*   m-d-yyyy or   d-m-yyyy */
    case 124:   /*  m-dd-yyyy or  d-mm-yyyy */
    case 214:   /*  mm-d-yyyy or  dd-m-yyyy */
    case 224:   /* mm-dd-yyyy or dd-mm-yyyy */

      if (order == S_DATE_ORDER_DMY)
        {
        dd = dtf[0].value;
        mm = dtf[1].value;
        }

      else
        {
        /* for S_DATE_ORDER_MDY, the field ordering is certain */
        /* for S_DATE_ORDER_YMD, the field ordering is not certain */
        /* but since YMD implies month before year, we assume this is right */

        mm = dtf[0].value;
        dd = dtf[1].value;
        }

      yy = dtf[2].value;
      break;

    case 194:   /*  d-Mon-yyyy : for alpha month, ordering is certain */
    case 294:   /* dd-Mon-yyyy : for alpha month, ordering is certain */

      dd = dtf[0].value;
      mm = dtf[1].value;
      yy = dtf[2].value;
      break;

    case 911:   /*  Mon-d-y  : ordering is confident */
    case 921:   /* Mon-dd-y  : ordering is confident */
    case 912:   /*  Mon-d-yy : ordering is confident */
    case 922:   /* Mon-dd-yy : ordering is confident */

      /* for alpha month, there is no such format as Mon-yy-dd */
      /* so, the mm-dd-yy ordering is reasonably certain */

      mm = dtf[0].value;
      dd = dtf[1].value;
      yy = dtf[2].value;

      /* year is 2-digit, so form the default century */
      /* we would normally have a cutoff of 1970, but in some cases */
      /* a conversion error will leave a time_t with Dec 31 1969 */
      /* so, values < 69 are assumed to be in the 21 century */

      if (yy < 69)
        {
        yy += 2000;
        }

      else
        {
        yy += 1900;
        }

      break;

    case 191:   /*  d-Mon-y  */
    case 291:   /* dd-Mon-y  */
    case 192:   /*  d-Mon-yy */
    case 292:   /* dd-Mon-yy */

      if (order == S_DATE_ORDER_YMD)
        {
        yy = dtf[0].value;
        mm = dtf[1].value;
        dd = dtf[2].value;
        }

      else
        {
        /* for S_DATE_ORDER_DMY, the field ordering is certain */
        /* for S_DATE_ORDER_MDY, the field ordering is not certain */
        /* but since MDY implies day before year, we assume this is right */

        dd = dtf[0].value;
        mm = dtf[1].value;
        yy = dtf[2].value;
        }

      /* year is 2-digit, so form the default century */
      /* we would normally have a cutoff of 1970, but in some cases */
      /* a conversion error will leave a time_t with Dec 31 1969 */
      /* so, values < 69 are assumed to be in the 21 century */

      if (yy < 69)
        {
        yy += 2000;
        }

      else
        {
        yy += 1900;
        }

      break;

    case 111:   /*   d-m-y      y-m-d       m-d-y   */
    case 112:   /*   d-m-yy     y-m-dd      m-d-yy  */

    case 121:   /*   d-mm-y     y-mm-d      m-dd-y  */
    case 122:   /*   d-mm-yy    y-mm-dd     m-dd-yy */

    case 211:   /*  dd-m-y     yy-m-d      mm-d-y   */
    case 212:   /*  dd-m-yy    yy-m-dd     mm-d-yy  */

    case 221:   /*  dd-mm-y    yy-mm-d     mm-dd-y  */
    case 222:   /*  dd-mm-yy   yy-mm-dd    mm-dd-yy */
    
     if (order == S_DATE_ORDER_DMY)
        {
        dd = dtf[0].value;
        mm = dtf[1].value;
        yy = dtf[2].value;
        }

      else if (order == S_DATE_ORDER_YMD)
        {
        yy = dtf[0].value;
        mm = dtf[1].value;
        dd = dtf[2].value;
        }

      else /* assume mm-dd-yy */
        {
        mm = dtf[0].value;
        dd = dtf[1].value;
        yy = dtf[2].value;
        }

      /* year is 2-digit, so form the default century */
      /* we would normally have a cutoff of 1970, but in some cases */
      /* a conversion error will leave a time_t with Dec 31 1969 */
      /* so, values < 69 are assumed to be in the 21 century */

      if (yy < 69)
        {
        yy += 2000;
        }

      else
        {
        yy += 1900;
        }

      break;

    default:
      return SD_FAILURE;

    } /* switch */

  if ( (mm < 1)
  ||   (mm > 12)
  ||   (dd < 1)
  ||   (dd > 31)
  ||   (yy < 1969) )
    {
    return SD_FAILURE;
    }

  dt->month = mm;
  dt->day   = dd;
  dt->year  = yy;
  dt->dateFound = SD_TRUE;

  return SD_SUCCESS;

  } /* storeDateValue */


/************************************************************************/
/*                          storeTimeValue                              */
/*                                                                      */
/* take a DATETIME_FIELD array, determine field order of a time, and    */
/* store into a _TS_DATETIME.  if DATETIME_FIELD array is valid, and    */
/* the fields appear to be in the correct order, return SD_SUCCESS,     */
/* else return SD_FAILURE.                                              */
/*                                                                      */
/************************************************************************/

ST_RET                      storeTimeValue (
  DATETIME_FIELD *          /*I*/ dtf, 
  _TS_DATETIME *            /*O*/ dt)
  {
  ST_INT32                  n = 0;
  ST_INT32                  len;
  ST_INT32                  hour = 0;
  ST_INT32                  min  = 0;
  ST_INT32                  zoneHour = 0;
  ST_INT32                  zoneMin  = 0;
  ST_INT32                  sec  = 0;
  ST_INT32                  mSec = 0;
  ST_INT32                  uSec = 0;
  ST_INT32                  nSec = 0;
  ST_CHAR                   work[32];


  if (dt == NULL)
    {
    return SD_FAILURE;
    }

  dt->hour = 0;
  dt->min  = 0;
  dt->sec  = 0;
  dt->mSec = 0;
  dt->uSec = 0;
  dt->nSec = 0;
  dt->timeFound = SD_FALSE;

  dt->zoneHour = 0;
  dt->zoneMin  = 0;
  dt->zoneCode = 0;

  if (dtf == NULL)
    {
    return SD_FAILURE;
    }

  /* hh:mm:ss[A] [.frac] [+hh:mm] yyyy-mm-dd */
  /* 0  1  2       3       4  5   6    7  8  */


  if ( (dtf[n].delim != ':')
  &&   (dtf[n].len   != 1)
  &&   (dtf[n].len   != 2) )
    {
    return SD_FAILURE;
    }

  hour = dtf[n++].value;

  /* store minutes, a required field */

  if ( (dtf[n].len != 1)
  &&   (dtf[n].len != 2) )
    {
    return SD_FAILURE;
    }

  min = dtf[n].value;

  /* see if seconds field is present */

  if (dtf[n].delim == ':')
    {
    n++;  /* look at seconds field */

    if ( (dtf[n].len != 1)
    &&   (dtf[n].len != 2) )
      {
      return SD_FAILURE;
      }

    sec = dtf[n].value;
    }

  /* see if AM/PM field is present, and validate hour */
  /* in AM/PM mode, hour cannot be 00 or > 12 */ 
  /* so 00:00 AM and 00:00 PM are illegal */
  /* as are 14:00 PM etc. */

  if (dtf[n].delim == 'A')
    {
    if ((hour < 1) || (hour > 12))
      {
      return SD_FAILURE;
      }

    if (hour == 12)
      {
      hour = 0;    /* 12:05 AM is really 00:00 */
      }
    }

  else if (dtf[n].delim == 'P')
    {
    if ((hour < 1) || (hour > 12))
      {
      return SD_FAILURE;
      }

    /* 12:05 PM is 12:05 (so hour == 12 is OK), but 1:05 PM is 13:05 */

    if (hour < 12)
      {
      hour += 12;
      }
    }

  /* if decimal point is followed by nothing, it is a null fraction */
  /* if so, treat this as the end of the string */
  /* for example, "12:34." is the same as "12:34" */

  if ( (dtf[n].delim == '.')
  &&   (dtf[n+1].len == 0) )
    {
    dtf[n].delim = ' ';   /* end of string */
    }

  if (dtf[n].delim == '.')
    {
    /* extract mSec */
    /* this requires normalizing the value to 3 digits */
    /* since the current field ends with a dot, there must be */
    /* a following field that is numeric */

    n++;    /* look at fraction field */

    len  = dtf[n].len;

    if (len > 9)
      {
      return SD_FAILURE;    /* malformed/missing mSec field */
      }

    /* express fraction as a count of milliseconds */
    /* method: pad or truncate ms field to 3 digits */

    strncpy (work, dtf[n].text, 3);
    work[3] = 0;
    strcat (work, "000");
    work[3] = 0;
    mSec = atoi (work);

    /* express fraction as a count of microseconds */
    /* method: pad or truncate ms field to 6 digits */

    strncpy (work, dtf[n].text, 6);
    work[6] = 0;
    strcat (work, "000000");
    work[6] = 0;
    uSec = atoi (work);

    /* express fraction as a count of nanoseconds */
    /* method: pad or truncate ms field to 9 digits */

    strncpy (work, dtf[n].text, 9);
    work[9] = 0;
    strcat (work, "000000000");
    work[9] = 0;
    nSec = atoi (work);
    }

  if (dtf[n].delim == 'Z')
    {
    dt->zoneCode = 'Z';
    }

  else if ( (dtf[n].delim == '+')      /* +hh:mm or +h:mm */
  ||        (dtf[n].delim == '-') )    /* -hh:mm or -h:mm */
    {
    dt->zoneCode = dtf[n].delim;
    /* look at zone hour, it may be 1 or 2 digits */
    n++;

    /* allow for zone of +hhmm or -hhmm if it's the last field */

	if ( (dtf[n].delim == ' ')
	||   (dtf[n].delim == 'T') )
	  {
          if (dtf[n].len != 4)
	    {
	    return SD_FAILURE;    /* in this format, exactly 4 digits needed */
	    }

          zoneHour = (dtf[n].value) / 100;
          zoneMin  = (dtf[n].value) % 100;
          }
	
        else
	  {
	  if ( (dtf[n].delim != ':')    /* zone hour must have minutes */
	  &&   (dtf[n].len   != 1)
	  &&   (dtf[n].len   != 2) )
	    {
		return SD_FAILURE;
		}

	  zoneHour = dtf[n++].value;    /* grab hours, index to minutes */

	  if (dtf[n].len != 2)
	    {
	    return SD_FAILURE;
	    }

	  zoneMin = dtf[n].value;

	  if ( (zoneHour <  0)
	  ||   (zoneHour > 23)
	  ||   (zoneMin  <  0)
	  ||   (zoneMin  > 59) )
	    {
	    return SD_FAILURE;
	    }
	  }
    }

  if ( (hour <  0)
  ||   (hour > 23)
  ||   (min  <  0)
  ||   (min  > 59)
  ||   (sec  <  0) 
  ||   (sec  > 59) )
    {
    return SD_FAILURE;
    }

  dt->hour = hour;
  dt->min  = min;
  dt->sec  = sec;
  dt->mSec = mSec;
  dt->uSec = uSec;
  dt->nSec = nSec;
  dt->timeFound = SD_TRUE;

  dt->zoneHour = zoneHour;
  dt->zoneMin  = zoneMin;

  return SD_SUCCESS;

  } /* storeTimeValue */


/************************************************************************/
/*                       getTsDateTime					*/
/* parse a string containing a time and date value, and produce a       */
/* struct tm value.  accepted formats are:                              */
/*                                                                      */
/*     time [fraction] date                                             */
/*     date time [fraction]                                             */
/*                                                                      */
/* date may be in the following formats:                                */
/*                                                                      */
/*     yyyy-mm-dd                                                       */
/*     yy-mm-dd     (may be used in some locales)                       */
/*     mm-dd-yy     (US format)                                         */
/*     mm-dd-yyyy   (US format)                                         */
/*     dd-mm-yy     (European format)                                   */
/*     dd-mm-yyyy   (European format)                                   */
/*                                                                      */
/*     yyyy-Mon-dd                                                      */
/*     Mon-dd-yy                                                        */
/*     Mon-dd-yyyy                                                      */
/*     dd-Mon-yyyy  (European format)                                   */
/*                                                                      */
/* when date format may be ambiguous, the system is queried as to the   */
/* default field ordering, based on the current locale.  for example,   */
/* 01-02-03 could be Jan 01 2003, 01 Feb 2003 or 2001 Feb 03.           */
/*                                                                      */
/* when date contains one 4-digit value, it is assumed to be a year.    */
/* this helps to unambiguate some values.                               */
/*                                                                      */
/* date delimiter may be '-' or '/'                                     */
/*                                                                      */
/* time may or may not have a fraction and/or a timezone                */
/*                                                                      */
/* max number of fields possible: 9, consisting of:                     */
/*   date:     3                                                        */
/*   time:     3                                                        */
/*   fraction: 1                                                        */
/*   timezone: 2                                                        */
/*                                                                      */
/************************************************************************/

static ST_RET               getTsDateTime (
  ST_CHAR *                 /*I*/  in_buf, 
  _TS_DATETIME *            /*IO*/ out_dt)
  {
  ST_INT32                  maxfield = 10;
  ST_INT32                  numfields = 0;
  ST_INT32                  i;
  ST_CHAR *                 buf;
  ST_CHAR                   prevDelim = ' ';
  ST_INT32                  datefield;
  ST_INT32                  timefield;
  DATETIME_FIELD            dtf[10] = {{0}};
  _TS_DATETIME              dt = {0};


  if ((in_buf == NULL) || (out_dt == NULL))
    {
    return SD_FAILURE;      /* bad parameters */
    }

  dt.order = out_dt->order;    /* copy date-ordering option */

  /* extract fields */
  /* prevDelim is set so that a '.' will allow the next field */
  /* to have 1 to 7 digits */

  buf = in_buf;

  for (i=0; i < maxfield; i++)
    {
    buf = getDateTimeField (buf, prevDelim, &dtf[i]);

    if (buf != NULL)
      {
      prevDelim = dtf[i].delim;
      numfields++;
      
      if (*buf == '(')
        {
        dt.pflags = buf;    /* start of quality-flag field */
        break;
        }
      }
    } /* for */

  if (numfields < 5)
    {
    return SD_FAILURE;    /* we were expecting year,mon,day,hour,min fields */
    }

  /* determine field order */

  if ( (dtf[0].delim == '-')
  &&   (dtf[1].delim == '-') )
    {
    /* first field is date */
    /* example field layout: */

    /* yyyy-mm-dd [T] hh:mm:ss[A] [.frac] [+hh:mm] */
    /* 0    1  2      3  4  5       6       7  8   */

    if ( (dtf[2].delim != ' ')
    &&   (dtf[2].delim != 'T') )
      {
      return SD_FAILURE;    /* bad delimiter */
      }

    datefield = 0;
    timefield = 3;
    } /* if - - found */

  else if (dtf[0].delim == ':')
    {
    /* first field is time (T code would not be present) */
    /* example field layout: */

    /* hh:mm:ss[A] [.frac] [Z|+hh:mm|-hh:mm] yyyy-mm-dd */
    /* 0  1  2       3         4  5          6    7  8  */

    /* ensure we have a valid time format */
    /* mm must be followed by AM/PM code, :ss or space, not a '-' */

    if ( (dtf[1].delim != ' ')
    &&   (dtf[1].delim != ':')
    &&   (dtf[1].delim != 'Z')
    &&   (dtf[1].delim != '+')
    &&   (dtf[1].delim != '-')
    &&   (dtf[1].delim != 'A')
    &&   (dtf[1].delim != 'P') )
      {
      return SD_FAILURE;    /* bad delimiter */
      }

    timefield = 0;
    datefield = 0;

    /* find date field by looking for two '-' delimiters */
    /* if numfields == 9, then last field is at [8] */
    /* so we look for pairs up to [numfields-1] */

    for (i = 2; i < numfields-1; i++)
      {
      if ( (dtf[i].delim   == '-')
      &&   (dtf[i+1].delim == '-') )
        {
        datefield = i;
        break;
        }
      } /* for */
 
    if (datefield == 0)
      {
      return SD_FAILURE;    /* cannot find date field */
      }

    } /* if : found */

  else
    {
    return SD_FAILURE;    /* cannot determine date-time vs. time-date */
    }

  /* populate dt structure with date and time values */

  if (storeDateValue (&dtf[datefield], &dt) != SD_SUCCESS)
    {
    return SD_FAILURE;    /* malformed date */
    }

  if (storeTimeValue (&dtf[timefield], &dt) != SD_SUCCESS)
    {
    return SD_FAILURE;    /* malformed time */
    }

  *out_dt = dt;

  return SD_SUCCESS;

  } /* getTsDateTime */


/************************************************************************/
/*                       tstrStringToTm					*/
/* parse a string containing a time and date value, and produce a       */
/* struct tm value.  method: convert to _TS_DATEITIME via getTsDateTime */
/* then convert result to struct tm.                                    */
/************************************************************************/

ST_RET tstrStringToTm (ST_CHAR *in_buf, struct tm *out_struct_tm)
  {
  _TS_DATETIME              dt = {0};

  struct tm                 w_struct_tm = {0};

  if ((in_buf == NULL) || (out_struct_tm == NULL))
    {
    return SD_FAILURE;      /* bad parameters */
    }

  /* compatibility with old time_str.c requires month-day-year ordering */

  dt.order = S_DATE_ORDER_MDY;

  if (getTsDateTime (in_buf, &dt) != SD_SUCCESS)
    {
    return SD_FAILURE;      /* bad parameters */
    }

  /*  convert the components to struct tm */

  w_struct_tm.tm_year  = dt.year - 1900;
  w_struct_tm.tm_mon   = dt.month - 1;
  w_struct_tm.tm_mday  = dt.day;
  w_struct_tm.tm_hour  = dt.hour;
  w_struct_tm.tm_min   = dt.min;
  w_struct_tm.tm_sec   = dt.sec;
  w_struct_tm.tm_isdst = -1;  	        /* let api determine DST */

  usr_mkgmtime (&w_struct_tm);          /* generate tm_wday, tm_yday */

  /* we do not know if supplied date was GMT or not, so DST is uncertain */
  w_struct_tm.tm_isdst = -1;  	        /* a reasonable default value */

  *out_struct_tm = w_struct_tm;
  
  return SD_SUCCESS;

  } /* tstrStringToTm */
  

/************************************************************************/
/*                       tstrStringToTime				*/
/* parse a string containing a time and date value, and produce a       */
/* time_t value.  method: convert to struct tm via tstrStringToTm,      */
/* then convert result to time_t.                                       */
/************************************************************************/

ST_RET tstrStringToTime (ST_CHAR *in_buf, time_t *out_time_t)
  {
  time_t                    w_time_t;
  struct tm                 w_struct_tm;

  if ((in_buf == NULL) || (out_time_t == NULL))
    {
    return SD_FAILURE;      /* bad parameters */
    }

  if (tstrStringToTm (in_buf, &w_struct_tm) != SD_SUCCESS)
    {
    return SD_FAILURE;      /* parse failed */
    }

  w_time_t = mktime (&w_struct_tm);

  if (w_time_t == (time_t) -1)
    {
    return SD_FAILURE;
    }

  *out_time_t = w_time_t;
  return SD_SUCCESS;

  } /* tstrStringToTime */


/************************************************************************/
/*                       tstrStringToTimeGmt                            */
/* time_t value.  method: convert to struct tm via tstrStringToTm,      */
/* then convert result to time_t using usr_mkgmtime().                  */
/************************************************************************/

ST_RET tstrStringToTimeGmt (ST_CHAR *in_buf, time_t *out_time_t)
  {
  time_t                    w_time_t;
  struct tm                 w_struct_tm;

  if ((in_buf == NULL) || (out_time_t == NULL))
    {
    return SD_FAILURE;      /* bad parameters */
    }

  if (tstrStringToTm (in_buf, &w_struct_tm) != SD_SUCCESS)
    {
    return SD_FAILURE;      /* parse failed */
    }

  w_time_t = usr_mkgmtime (&w_struct_tm);

  if (w_time_t == (time_t) -1)
    {
    return SD_FAILURE;
    }

  *out_time_t = w_time_t;
  return SD_SUCCESS;

  } /* tstrStringToTimeGmt */


/************************************************************************/
/*                       tstrTmToString					*/
/* Convert struct tm to string.						*/
/************************************************************************/

ST_RET tstrTmToString (struct tm *t, ST_CHAR *dest)
  {
  ST_CHAR *timeFormatStr = "%m-%d-%Y %H:%M:%S";
  strftime (dest, 30, timeFormatStr, t);
  return (SD_SUCCESS);
  }


#if 0
/* obsoleted code */

/************************************************************************/
/*			parseDateString					*/
/************************************************************************/

/* parseDateString IS NO LONGER REFERENCED IN THIS CODE */

static ST_RET parseDateString (ST_CHAR *s, _TS_DATETIME *dt)
  {
ST_CHAR *p;
ST_CHAR *d1;
ST_CHAR *d2;
ST_CHAR *d3;
int century;

  dt->dateFound = SD_TRUE;

  /* break into three substrings */
  d1 = s;
  p =  strpbrk (s,"-");
  *p = 0;
  d2 = ++p;
  p =  strpbrk (d2,"-");
  if (!p)
    return (SD_FAILURE);
  *p = 0;
  d3 = ++p;

  /* Now process each date sub-substring seperately  */

  /* Check for alpha month forms */
  if (isalpha (*d1))
    {
    if (strToMonth (d1, &dt->month))
      return (SD_FAILURE);
    if (!sscanf (d2, "%d", &dt->day))
      return (SD_FAILURE);
    }
  else if (isalpha (*d2))
    {
    if (!sscanf (d1, "%d", &dt->day))
      return (SD_FAILURE);
    if (strToMonth (d2, &dt->month))
      return (SD_FAILURE);
    }
  else 	/* Not an ALPHA month form */
    {
    /* Numeric month is d1 */
    if (!sscanf (d1, "%d", &dt->month))
      return (SD_FAILURE);
    --dt->month;			/* we use 0-11 for month */

    /* Numeric day is d2 */
    if (!sscanf (d2, "%d", &dt->day))
      return (SD_FAILURE);
    }

  /* Numeric year is always d3 */
  if (!sscanf (d3, "%d", &dt->year))
    return (SD_FAILURE);
  if (strlen (d3) == 2)
    {
    /* any year < 1984 is assumed to be in the 21st century	*/
    century = (dt->year < 84) ? 2000 : 1900;
    dt->year += century;
    }
  return (SD_SUCCESS);
  }


/* end of obsoleted code */
#endif


#if 0
/* obsoleted code */


/************************************************************************/
/*			parseTimeString					*/
/************************************************************************/

/* parseTimeString IS NO LONGER REFERENCED IN THIS CODE */

static ST_RET parseTimeString (ST_CHAR *s, _TS_DATETIME *dt)
  {
int pm;
ST_CHAR *p, *t1, *t2, *t3;

  dt->timeFound = SD_TRUE;

  /* break into two substrings */
  p =  strpbrk (s,":");
  t1 = s;  		/* hours are here */
  *p = 0;
  t2 = ++p;		/* minutes are here */
  if (!p)
    return (SD_FAILURE);

  p = strpbrk (t2, ":");
  if (!p)
    t3 = NULL;
  else
    {
    *p = 0;
    t3 = ++p;
    }

  pm = SD_FALSE;    
  if (t3)
    {
    if (t3[2] == 'A' || t3[2] == 'a')
      {
      t3[2] = 0;
      }
    else if (t3[2] == 'P' || t3[2] == 'p')
      {
      pm = SD_TRUE;    
      t3[2] = 0;
      }
    }

  if (!sscanf (t1, "%d", &dt->hour))
    return (SD_FAILURE);

  if (!sscanf (t2, "%d", &dt->min))
    return (SD_FAILURE);

  if (t3)
    sscanf (t3, "%d", &dt->sec);
  else
    dt->sec = 0;

  if (pm)
    dt->hour += 12;

  return (SD_SUCCESS);
  }


/* end of obsoleted code */
#endif


/************************************************************************/

#if 0
/* obsoleted code */

static ST_CHAR *monthStrings[12] =
  {
  "Jan",
  "Feb",
  "Mar",
  "Apr",
  "May",
  "Jun",
  "Jul",
  "Aug",
  "Sep",
  "Oct",
  "Nov",
  "Dec"
  };


static ST_RET strToMonth (ST_CHAR *s, int *monthOut)
  {
int i;

  for (i = 0; i < 12; ++i)
    {
    if (!stricmp (s, monthStrings[i]))
      {
      *monthOut = i;
      return (SD_SUCCESS);
      }
    }
  return (SD_FAILURE);
  }

#endif


/************************************************************************/
/* Btime6 Conversion Routines:						*/
/************************************************************************/

#define SEC_PER_MIN	60
#define SEC_PER_HOUR	(60 * SEC_PER_MIN)
#define SEC_PER_DAY	(24 * SEC_PER_HOUR)
#define MSEC_PER_SEC	1000


/************************************************************************/
/* Btime6StringToVals:  Receive string in BTIME_DEF_TIME_FORMAT+mSec	*/
/*			and return the number of days and msecs 	*/
/*			since Jan 1, 1984 midnight			*/
/*                                                                      */
/* format BTIME_DEF_TIME_FORMAT.mSec is 'mm-dd-yyyy hh:mm:ss.mmm'       */
/* NOTE: output values are NOT adjusted for timezone or DST             */
/*                                                                      */
/* method:  parse string, capturing mSec.  create time_t, and adjust    */
/* for difference in seconds between 1970-01-01 and 1984-01-01.         */
/************************************************************************/

ST_RET Btime6StringToVals (ST_CHAR *src, ST_INT32 *numDays, ST_INT32 *numMSec)
  {
  _TS_DATETIME              dt = {0};
  struct tm                 w_struct_tm = {0};

  time_t                    w_time_t;
  time_t                    w_days;
  time_t                    w_msec;

  if ((src == NULL) || (numDays == NULL) || (numMSec == NULL))
    {
    return SD_FAILURE;      /* bad parameters */
    }

  /* for Btime strings, month-day-year date ordering is required */

  dt.order = S_DATE_ORDER_MDY;

  if (getTsDateTime (src, &dt) != SD_SUCCESS)
    {
    SLOGALWAYS1 ("Btime6StringToVals: Unable to parse Btime6 '%s'", src);
    return SD_FAILURE;      /* bad parameters */
    }

  /*  convert the components to struct tm */

  w_struct_tm.tm_year  = dt.year - 1900;
  w_struct_tm.tm_mon   = dt.month - 1;
  w_struct_tm.tm_mday  = dt.day;
  w_struct_tm.tm_hour  = dt.hour;
  w_struct_tm.tm_min   = dt.min;
  w_struct_tm.tm_sec   = dt.sec;
  w_struct_tm.tm_isdst = 0;  	        /* BTime is GMT based */

  w_time_t = usr_mkgmtime (&w_struct_tm);    /* portable GMT-based mktime() */

  if (w_time_t == (time_t) -1) 
    {
    SLOGALWAYS1 ("Btime6StringToVals: Unable to convert '%s' to time_t", src);
    return SD_FAILURE;
    }

  /* adjust for diff between 1984 (Btime epoch) and 1970 (Unix time epoch) */
  /* this is a known constant amount, so we don't recalculate it each time */

  w_time_t -= (time_t) S_SECS_DIFF_1984_1970;

  w_days = w_time_t / SEC_PER_DAY;
  w_msec = (w_time_t % SEC_PER_DAY) * 1000; /* EJV CORR: the multiplication by 1000 was missing */

  /* save the calculated time value */
  *numDays = (ST_UINT32) w_days;
  *numMSec = (ST_UINT32) w_msec + (ST_UINT32) dt.mSec;

  return SD_SUCCESS;

  } /* Btime6StringToVals */


/************************************************************************/
/* Btime6ValsToString:  Receive the number of days and msecs since	*/
/*			Jan 1, 1984 - midnight and return a string in	*/
/*			BTIME_DEF_TIME_FORMAT+mSec			*/
/************************************************************************/

ST_RET Btime6ValsToString (char *dest, ST_INT32 numDays, ST_INT32 numMSec)
  {
struct tm tmVal;
time_t tmRslt;
long numSeconds, balMSec;
char stash[MAX_TIME_STRING_LEN+1];
ldiv_t divResult;
_TS_DATETIME dt;

  divResult = ldiv (numMSec, MSEC_PER_SEC);
  numSeconds = divResult.quot;
  balMSec = divResult.rem;

  divResult = ldiv (numSeconds, SEC_PER_DAY);
  dt.day = divResult.quot + numDays;
  numSeconds = divResult.rem;

  divResult = ldiv (numSeconds, SEC_PER_HOUR);
  dt.hour = divResult.quot;
  numSeconds = divResult.rem;

  divResult = ldiv (numSeconds, SEC_PER_MIN);
  dt.min = divResult.quot;
  numSeconds = divResult.rem;

  dt.sec = numSeconds;

  /* set up the struct tm	*/
  tmVal.tm_wday = 0;		/* this is an output parameter */
  tmVal.tm_yday = 0;		/* this is an output parameter */

  tmVal.tm_year = 84;
  tmVal.tm_mon = 0;
  tmVal.tm_mday = dt.day+1;
  tmVal.tm_hour = dt.hour;
  tmVal.tm_min = dt.min;
  tmVal.tm_sec = dt.sec;
  tmVal.tm_isdst = -1;  	/* let function guess */
  tmRslt = mktime(&tmVal);

  if (tmRslt == (time_t) -1) 
    return (SD_FAILURE);

  /* now turn it into a string	with out mSeconds 			*/
  strftime (stash, MAX_TIME_STRING_LEN, BtimeTimeFormat, localtime (&tmRslt));

  /* now append the mSeconds to the string				*/
  sprintf (dest, "%s.%03ld", stash, balMSec);

  return (SD_SUCCESS);

  } /* Btime6ValsToString */


/************************************************************************/
/* Btime4StringToVals:  Receive string in BTIME_DEF_TIME_FORMAT.mSec	*/
/*			and return the number of msecs since midnight	*/
/* format BTIME_DEF_TIME_FORMAT.mSec is 'mm-dd-yyyy hh:mm:ss.mmm'       */
/************************************************************************/

ST_RET Btime4StringToVals (char *src, ST_INT32 *numMSec)
  {
  _TS_DATETIME              dt = {0};
  struct tm  *              p_struct_tm = {0};

  time_t                    w_time_t;
  ST_INT32                  curDay;
  ST_INT32                  curMonth;
  ST_INT32                  curYear;
  ST_INT32                  totalMsec;


  if ((src == NULL) || (numMSec == NULL))
    {
    return SD_FAILURE;      /* bad parameters */
    }

  /* get current time for validation of date */

  w_time_t = time (NULL);

  if (w_time_t == (time_t) -1) 
    {
    SLOGALWAYS0 ("Btime4StringToVals: Unable to obtain current time");
    return SD_FAILURE;      /* normally should not occur */
    }

  /* current time is converted to a GMT-based struct tm */
  /* because the Btime6 version of this function calculates the offset */
  /* from 1984-01-01 GMT, so we use a GMT-based struct tm here to be */
  /* consistent. */

  p_struct_tm = gmtime (&w_time_t);

  if (p_struct_tm == NULL) 
    {
    SLOGALWAYS0 (
      "Btime4StringToVals: Unable to convert current time_t to struct tm");
    return SD_FAILURE;      /* normally should not occur */
    }

  curYear  = p_struct_tm->tm_year + 1900;
  curMonth = p_struct_tm->tm_mon  + 1;
  curDay   = p_struct_tm->tm_mday;

  /* for Btime strings, month-day-year date ordering is required */

  dt.order = S_DATE_ORDER_MDY;

  if (getTsDateTime (src, &dt) != SD_SUCCESS)
    {
    SLOGALWAYS1 ("Btime4StringToVals: Unable to parse Btime4 '%s'", src);
    return SD_FAILURE;      /* bad parameters */
    }

  if ( (dt.year  != curYear )
  ||   (dt.month != curMonth)
  ||   (dt.day   != curDay  ) )
    {
    SLOGALWAYS0 ("Btime4 Conversion Error: Input date must be today's date");
    return SD_FAILURE;
    }

  /* get mSecs from hours/min/secs */

  totalMsec = (dt.hour * SEC_PER_HOUR * MSEC_PER_SEC)
            + (dt.min  * SEC_PER_MIN  * MSEC_PER_SEC)
            + (dt.sec                 * MSEC_PER_SEC)
            + (dt.mSec);

  /* ensure mSecs does not exceed num mSecs in a day.  the count must not */
  /* equal ms/day either, otherwise it would be an offset to the next day */

  if ( (totalMsec < 0)
  ||   (totalMsec >= (SEC_PER_DAY * MSEC_PER_SEC)) )
    {
    SLOGALWAYS1 ("Btime4 Conversion Error: Invalid millisecond count: %d", totalMsec);
    return SD_FAILURE;
    }

  *numMSec = totalMsec;

  return SD_SUCCESS;

  } /* Btime4StringToVals */


/************************************************************************/
/* Btime4ValsToString: Receive number of msecs since midnight and return*/
/*		       string in BTIM_DEF_TIME_FORMAT.mSec		*/
/************************************************************************/

ST_RET Btime4ValsToString (char *dest, ST_INT32 numMSec)
  {
int curDay, curMonth, curYear;
struct tm tmVal, *curTime;
time_t tmRslt;
long numSeconds, balMSec;
char stash[MAX_TIME_STRING_LEN+1];
ldiv_t divResult;
_TS_DATETIME dt;
time_t theTime;

  /* figure out todays date */
  theTime = time (NULL);
  curTime = localtime (&theTime);
  curDay = curTime->tm_mday;
  curMonth = curTime->tm_mon;
  curYear = curTime->tm_year;

  divResult = ldiv (numMSec, MSEC_PER_SEC);
  numSeconds = divResult.quot;
  balMSec = divResult.rem;

  divResult = ldiv (numSeconds, SEC_PER_HOUR);
  dt.hour = divResult.quot;
  numSeconds = divResult.rem;

  divResult = ldiv (numSeconds, SEC_PER_MIN);
  dt.min = divResult.quot;
  numSeconds = divResult.rem;

  dt.sec = numSeconds;

  /* set up the struct tm	*/
  tmVal.tm_wday = 0;		/* this is an output parameter */
  tmVal.tm_yday = 0;		/* this is an output parameter */

  tmVal.tm_year = curYear;
  tmVal.tm_mon = curMonth;
  tmVal.tm_mday = curDay;
  tmVal.tm_hour = dt.hour;
  tmVal.tm_min = dt.min;
  tmVal.tm_sec = dt.sec;
  tmVal.tm_isdst = -1;  	/* let function guess */
  tmRslt = mktime(&tmVal);

  if (tmRslt == (time_t) -1) 
    return (SD_FAILURE);

  /* now turn it into a string	with out mSeconds 			*/
  strftime (stash, MAX_TIME_STRING_LEN, BtimeTimeFormat, localtime (&tmRslt));

  /* now append the mSeconds to the string				*/
  sprintf (dest, "%s.%03ld", stash, balMSec);

  return (SD_SUCCESS);
  }


#if 0
/* obsoleted code */

/************************************************************************/
/* parseBtimeString:  parse HH:MM:SS:msec				*/
/************************************************************************/

/* parseBtimeString IS NO LONGER REFERENCED IN THIS CODE */

static ST_RET parseBtimeString (char *s, _TS_DATETIME *dt)
  {
char stash[MAX_TIME_STRING_LEN+1];
char toFind[] = ":.";
char *token;
int count = 0;

  dt->timeFound = SD_TRUE;

  /* make a copy first */
  strcpy (stash, s);

  /* set default values */
  dt->hour = dt->min = dt->sec = dt->mSec = 0;

  /* do the token thing to separate the string */
  token = strtok (stash, toFind);
  while (token != NULL)
    {
    switch (count)
      {
      case 0:
        dt->hour = atoi (token);
	break;
      case 1:
        dt->min = atoi (token);
	break;
      case 2:
        dt->sec = atoi (token);
	break;
      case 3:
        dt->mSec = atoi (token);
	break;
      }
    count++;
    /* get next token */
    token = strtok (NULL, toFind);
    }
  
  return (SD_SUCCESS);
  }


/* end of obsoleted code */
#endif


/************************************************************************/
/*                          validQualField                              */
/* verify that a string contains a valid Quality field (qual=b,b,b,n)   */
/* there must be at least 3 bit flags present, and proper comma delims. */
/* if valid, return number characterizing the field, else return 0.     */
/* then, create a buffer with the extracted flags and 'n' value,        */
/* with default values if trailing flags or ''n' value is omitted.      */
/************************************************************************/

static ST_INT32             validQualField (
  ST_CHAR *                 qual,
  ST_CHAR *                 flagBuf)
  {
  ST_CHAR                   work[32];
  ST_INT32                  i;
  ST_INT32                  digit = 0;

  if ((qual == NULL)  | (flagBuf == NULL))
    {
    return 0;
    }

  /* (qual=1,2,3,n) */
  /* 0123456789012345 */
  /*           111111 */

  strcpy (flagBuf, "00000");    /* create defaults */

  /* make copy of string, modifying it to create a pattern to verify */

  for (i=0; i < 24; i++)
    {
    if (qual[i] <= ' ')
      {
      break;
      }

    else if (qual[i] == ')')
      {
      work[i++] = ')';
      break;
      }

    else if (isdigit (qual[i]))
      {
      /* first 3 digits must be 0 or 1, rest is value 0 to 31 */
      digit++;

      if (digit <= 3)
        {
        if (qual[i] > '1')
          {
          return 0;    /* char is digit but > '1', not a valid bit value */
          }
        work[i] = '1';    /* '1' stands for '0' or '1' */
        }

      else    /* digits after 3rd */
        {
        work[i] = '9';    /* to check pattern */
        }

      } /* digit */

    else
      {
      work[i] = (ST_CHAR) toupper (qual[i]);
      }

    } /* for */

  work[i] = 0;
  
                      
  if      (strcmp (work, "(QUAL=1,1,1)") == 0)
    {
    flagBuf[0] = qual[6];
    flagBuf[1] = qual[8];
    flagBuf[2] = qual[10];
    return 3;
    }                     
  else if (strcmp (work, "(QUAL=1,1,1,9)") == 0)
    {
    flagBuf[0] = qual[6];
    flagBuf[1] = qual[8];
    flagBuf[2] = qual[10];
    flagBuf[3] = qual[12];    /* 1-digit 'n' value */
    flagBuf[4] = 0;           /* shorten return value */

    /* when 'n' value is 1-digit, any digit value is OK */
    return 4;
    }
  else if (strcmp (work, "(QUAL=1,1,1,99)") == 0)
    {
    ST_INT32                num;

    flagBuf[0] = qual[6];
    flagBuf[1] = qual[8];
    flagBuf[2] = qual[10];
    flagBuf[3] = qual[12];    /* 2-digit 'n' value */
    flagBuf[4] = qual[13];

    /* when 'n' value is 1-digit, check for range 0 to 31 */
    /* the 'n' value is supposed to be a 5-bit value */

    num = atoi (flagBuf + 3);

    if ((num < 0) || (num > 31))
      {
      return 0;    /* 'n' value is out of range */
      }

    return 6;
    }


  /* quality field was none of the above formats */

  return 0;

  } /* validQualField */


/************************************************************************/
/* UTC Time Conversion Functions:                                       */
/*        Format = YYYY-MM-DDThh:mm:ss.000000000Z(qual=b,b,b,b,n)       */
/************************************************************************/
/* UtcStringToVals:                                                     */
/************************************************************************/

ST_RET UtcStringToVals (
  ST_CHAR *                 src, 
  ST_UINT32 *               pSecs, 
  ST_UINT32 *               pFraction, 
  ST_UINT32 *               pQflags)
  {
  _TS_DATETIME              dt = {0};

  struct tm                 w_struct_tm = {0};
  time_t                    w_time_t;
  ST_CHAR *                 pflags;
  ST_CHAR                   flagBuf[8];

  ST_INT                    b0;
  ST_INT                    b1;
  ST_INT                    b2;
  ST_INT                    bx;

  ST_DOUBLE                 decSecs;
  ST_DOUBLE                 binSecs;


  if ( (src       == NULL) 
  ||   (pSecs     == NULL)
  ||   (pFraction == NULL)
  ||   (pQflags   == NULL) )
    {
    SLOGALWAYS0 ("UtcStringToVals: NULL parameters");
    return SD_FAILURE;      /* bad parameters */
    }

  /* for UTC time strings, date ordering is yyyy-mm-dd*/
  dt.order = S_DATE_ORDER_YMD;
  dt.pflags = NULL;

  if (getTsDateTime (src, &dt) != SD_SUCCESS)
    {
    SLOGALWAYS1 ("UtcStringToVals: Unable to parse time '%s'", src);
    return SD_FAILURE;      /* bad parameters */
    }

  if (dt.zoneCode != 'Z')
    {
    SLOGALWAYS1 ("UtcStringToVals: GMT timezone code Z missing in '%s'", src);
    return SD_FAILURE;
    }
    
  pflags = dt.pflags;    /* pointer to (qual string */

  if (pflags == NULL)
    {
    /* quality fields were not present, assume they are all 0 */
    /* SLOGALWAYS1 ("UtcStringToVals: Missing quality in '%s'", src); */

    *pQflags = 0;
    }
  else if (validQualField (pflags, flagBuf) < 3)    /* not enough flags */
    {
    SLOGALWAYS1 ("UtcStringToVals: Invalid quality in '%s'", src);
    return SD_FAILURE;
    }
  else
    {
    /* flags are already validated as '0' or '1' char values, so AND to get */
    /* bit values, and shift into correct positions */
   
    b0 = (flagBuf[0] & 1) << 7;
    b1 = (flagBuf[1] & 1) << 6;
    b2 = (flagBuf[2] & 1) << 5;
    bx = atoi (flagBuf + 3);

    *pQflags = (b0 | b1 | b2 | bx);
    }

  /*  convert the components to struct tm */
  w_struct_tm.tm_year  = dt.year - 1900;
  w_struct_tm.tm_mon   = dt.month - 1;
  w_struct_tm.tm_mday  = dt.day;
  w_struct_tm.tm_hour  = dt.hour;
  w_struct_tm.tm_min   = dt.min;
  w_struct_tm.tm_sec   = dt.sec;
  w_struct_tm.tm_isdst = 0;

  w_time_t = usr_mkgmtime (&w_struct_tm);    /* portable GMT-based mktime() */

  if (w_time_t == (time_t) -1) 
    {
    SLOGALWAYS1 ("UtcStringToVals: Unable to convert '%s' to time_t", src);
    return SD_FAILURE;
    }

  /* save the calculated time value */
  *pSecs = (ST_UINT32) w_time_t;

  /* set the decimal fraction of seconds */
  /* dt.nSec contains a count of nanoseconds */
  /* this must be converted to a 24-bit binary fraction of fractional time */

  /* note: 0x1000000 == 16777216 */

  /* form decimal fraction of 1 second from 0.0 to 0.999,999,999 */

  /*                                 123456789                   */
  decSecs = ((ST_DOUBLE) dt.nSec) / 1000000000.0;

  /* convert to binary fraction of 1 second from 0x0.0 to 0x0.FFFFFF */
  binSecs = (decSecs * 16777216.0) + 0.5;

  /* return int equivalent count to caller */
  *pFraction = (ST_UINT32) binSecs;

  return SD_SUCCESS;

  } /* UtcStringToVals */


/************************************************************************/
/* UtcValsToString:                                                     */
/************************************************************************/

ST_RET UtcValsToString (char *dest, ST_UINT32 secs, ST_UINT32 fraction, 
                        ST_UINT32 qflags)
  {
ST_CHAR theDate[MAX_TIME_STRING_LEN];
ST_CHAR theFraction[25];
ST_CHAR theQual[25];
ST_DOUBLE dFraction;
ST_CHAR *pFract;

ST_CHAR b0, b1, b2;
ST_INT rest;

time_t t = secs;
struct tm *pTm;

  /* get the date portion */

  pTm = gmtime (&t);

  if (!pTm)
    {
    SLOGALWAYS0 ("UtcValsToString:  conversion failure - invalid seconds.");
    return SD_FAILURE;
    }

  strftime (theDate, MAX_TIME_STRING_LEN, UTC_DEF_TIME_FORMAT, pTm);

  /* get the fraction portion */

  dFraction = ((ST_DOUBLE) fraction / (ST_DOUBLE) 0x01000000);
  sprintf (theFraction, " %#0.09f", dFraction);
  pFract = strchr (theFraction, '.');

  if (!pFract)
    {
    SLOGALWAYS1 ("UtcToString - unable to convert fraction %d", fraction);
    return SD_FAILURE;
    }

  /* get the qflags */

  b0 = b1 = b2 = rest ='0';
  
  if (qflags & 0x80) b0 = '1';
  if (qflags & 0x40) b1 = '1';
  if (qflags & 0x20) b2 = '1';
  rest = (qflags & 0x1F);
  sprintf (theQual, "Z(qual=%c,%c,%c,%d)", b0, b1, b2, rest);

  /* put them together */
  sprintf (dest, "%s%s%s", theDate, pFract, theQual);

  return SD_SUCCESS;
  }


/************************************************************************/
/* XmlStringToUtcValue                                                  */
/************************************************************************/
/*   An input time and date string is converted to the number of        */
/*   seconds since 1/1/1970.                                            */
/*                                                                      */ 
/*   Any of the following strings are valid input to this subroutine:	*/
/*									*/
/*     "yyyy-mm-ddThh:mm:ss.fffff+/-hh:mm"   				*/
/*     "yyyy-mm-ddThh:mm:ss+/-hh:mm"	      				*/
/*									*/
/*     "yyyy-mm-ddThh:mm:ss.fffffZ"	 				*/
/*     "yyyy-mm-ddThh:mm:ssZ"		 				*/
/*									*/
/*     "yyyy-mm-ddThh:mm:ss.fffff"	 				*/
/*     "yyyy-mm-ddThh:mm:ss"		  				*/
/*									*/
/*     Note:	                                                        */
/*       Decimal fraction for microseconds:                     .fffff  */
/*       East time zone offset from GMT (Greenwich Mean Time):  +hh:mm  */
/*       West time zone offset from GMT (Greenwich Mean Time):  -hh:mm  */
/*									*/
/*                                                                      */
/* Output is stored in SX_DATE_TIME structure as:                       */
/*                                                                      */
/*    dateTime	        stored as number of seconds elapsed since       */
/*    		        midnight (00:00:00) January 1, 1970,            */
/*		        UTC (Coordinated Universal Time), according     */
/*		        to the system clock                             */
/*    useMicroseconds   indicates decimal fraction for seconds was      */
/*                      specified                                       */
/*    microseconds      specified decimal fraction of seconds stored as */
/*                      microseconds                                    */
/*    useTZ             indicates a time zone offset is present         */
/*    tz                time zone offset specified as minutes           */
/*									*/
/* Time zone offset "tz" and time zone presence "useTZ" will be         */
/* specified in output as follows:                                      */
/*     									*/
/*  "yyyy-mm-ddThh:mm:ss.fffff+/-hh:mm" 'useTZ = SD_TRUE'               */
/*                                      'tz = +/-seconds'               */	        
/*  "yyyy-mm-ddThh:mm:ss+/-hh:mm"       'useTZ = SD_TRUE'               */ 
/*                                      'tz = +/-seconds'	        */
/*									*/
/*  "yyyy-mm-ddThh:mm:ss.fffffZ"	'useTZ = SD_TRUE'  'tz = 0' 	*/
/*  "yyyy-mm-ddThh:mm:ssZ"	        'useTZ = SD_TRUE'  'tz = 0' 	*/
/*								 	*/
/*  "yyyy-mm-ddThh:mm:ss.fffff"	        'useTZ = SD_FALSE' 'tz ignored' */
/*  "yyyy-mm-ddThh:mm:ss"	        'useTZ = SD_FALSE' 'tz ignored' */
/*                                  					*/
/************************************************************************/

ST_RET XmlStringToUtcValue (ST_CHAR *in_buf, SX_DATE_TIME *sxDateTime)
  {
  _TS_DATETIME              dt = {0};

  struct tm                 w_struct_tm = {0};
  time_t                    w_time_t;    /* local time in seconds */


  if ((in_buf == NULL) || (sxDateTime == NULL))
    {
    SLOGALWAYS0 ("XmlStringToUtcValue: NULL parameters");
    return SD_FAILURE;      /* bad parameters */
    }

  /* split up the string input from XML file so we can store */
  /* number of seconds, number of microseconds (decimal      */
  /* fraction of seconds) and number of minutes in time zone */
  /* offset separately                                       */

  /* for XML time strings, date ordering is yyyy-mm-dd*/

  dt.order = S_DATE_ORDER_YMD;

  if (getTsDateTime (in_buf, &dt) != SD_SUCCESS)
    {
    return SD_FAILURE;      /* bad parameters */
    }

  /*  convert the components to struct tm */

  w_struct_tm.tm_year  = dt.year - 1900;
  w_struct_tm.tm_mon   = dt.month - 1;
  w_struct_tm.tm_mday  = dt.day;
  w_struct_tm.tm_hour  = dt.hour;
  w_struct_tm.tm_min   = dt.min;
  w_struct_tm.tm_sec   = dt.sec;

  /* set the decimal fraction of seconds if present */

  if (dt.uSec == 0)
    {
    sxDateTime->microseconds = 0;
    sxDateTime->useMicroseconds = SD_FALSE;
    }

  else
    {
    sxDateTime->microseconds = dt.uSec;
    sxDateTime->useMicroseconds = SD_TRUE;
    }
 
  /* get the time zone offset in minutes if present */

  if (dt.zoneCode)    /* '+', '-' or 'Z' present */
    {
    sxDateTime->useTZ = SD_TRUE;
    sxDateTime->tz = (dt.zoneHour * 60) + dt.zoneMin;
    
    if (dt.zoneCode == '-')
      {
      sxDateTime->tz = -sxDateTime->tz;
      }
    }

  else
    {
    sxDateTime->useTZ = SD_FALSE;
    sxDateTime->tz = 0;
    }

  /* calculate total seconds in UTC time so we can store it */
  /* as a number of seconds since 01/01/1970 */
  
  if (sxDateTime->useTZ && sxDateTime->tz == 0)
    {
    w_struct_tm.tm_isdst = 0;
    w_time_t = usr_mkgmtime (&w_struct_tm);    /* portable GMT-based mktime() */
    }

  else
    {
    w_struct_tm.tm_isdst = -1; 
    w_time_t = mktime (&w_struct_tm);
    }

  if (w_time_t == (time_t) -1) 
    {
    SLOGALWAYS1 ("XmlStringToUtcValue: Unable to convert time '%s'", in_buf);
    return SD_FAILURE;
    }

  /* save the calculated time value */
  sxDateTime->dateTime = w_time_t;

  return SD_SUCCESS;
  
  } /* XmlStringToUtcValue */


/************************************************************************/
/* UtcValueToXmlString                                                  */
/************************************************************************/  
/*  The specified number of seconds since 1/1/1970 is converted to a    */
/*  time and date string.                                               */
/*                                                                      */
/*  Input is stored in "SX_DATE_TIME" as:                               */
/*                                                                      */
/*    dateTime	        stored as number of seconds elapsed since       */
/*    		        midnight (00:00:00) January 1, 1970,            */
/*		        UTC (Coordinated Universal Time), according     */
/*		        to the system clock                             */
/*    useMicroseconds   indicates decimal fraction of seconds was       */
/*                      specified                                       */
/*    microseconds      decimal fraction of seconds specified stored as */
/*                      microseconds                                    */
/*    useTZ             indicates a time zone offset is present         */
/*    tz                time zone offset from GMT (Greenwich Mean Time) */
/*                      specified as minutes                            */
/*									*/
/*  Output string format will depend upon values present in the         */
/*  "SX_DATE_TIME" structure as follows:                                */
/*                                                                      */
/*  'useTZ = SD_TRUE' 'tz = nn..n'  "yyyy-mm-ddThh:mm:ss.fffff+/-hh:mm" */
/*  'useTZ = SD_TRUE' 'tz = nn..n'  "yyyy-mm-ddThh:mm:ss+/-hh:mm"       */
/*									*/
/*  'useTZ = SD_TRUE'  'tz = 0'     "yyyy-mm-ddThh:mm:ss.fffffZ"	*/
/*  'useTZ = SD_TRUE'  'tz = 0'     "yyyy-mm-ddThh:mm:ssZ"		*/
/*									*/
/*  'useTZ = SD_FALSE' 'tz ignored' "yyyy-mm-ddThh:mm:ss.fffff"		*/
/*  'useTZ = SD_FALSE' 'tz ignored' "yyyy-mm-ddThh:mm:ss"		*/
/*									*/
/************************************************************************/

ST_RET UtcValueToXmlString (ST_CHAR *dest, ST_UINT destLen,
                            SX_DATE_TIME *sxDateTime)
  {
ST_CHAR theFraction[64], theTZ[64];
ST_CHAR tzSign = '+';
ST_INT  tzValue;

#define MAX_DATE_TIME_STR_LEN	32	/* longest output format:		*/
					/*  "yyyy-mm-ddThh:mm:ss.fffff+/-hh:mm"	*/

  if (destLen < MAX_DATE_TIME_STR_LEN)
    {
    SLOGALWAYS2 ("ERROR: Buffer %d bytes may be too small for XML string (min=%d)",
      destLen, MAX_DATE_TIME_STR_LEN);
    return (SD_FAILURE);
    }

  /* get the date and time from the "dateTime" or 
     number of seconds from 1/1/1970 (UTC time) 
     stored in "sxDateTime" and place it in a
     structure of type "tm" returned from the call
     to "gmtime" as:
        sec
        min
        hour
        day of month
        month 
        year
        day of week
        day of year                                     */

  /* from the data stored in the structure utilize 
     "strftime" to format a date and time string using 
     the following output format:
	%Y is year with century as a decimal number
	%m is month as a decimal number (01-12)
	%d is day of month as a decimal number (01-31)
	%H is hour in 24 hour format (00-23)
	%M is minute as decimal number (00-59)
	%S is second as decimal number (00-59)	        */

  if (sxDateTime->useTZ && sxDateTime->tz == 0)
    strftime (dest, destLen, "%Y-%m-%dT%H:%M:%S", gmtime (&sxDateTime->dateTime));
  else
    strftime (dest, destLen, "%Y-%m-%dT%H:%M:%S", localtime (&sxDateTime->dateTime));

  /* then get number of microseconds stored as an       */
  /* "long" value in the "sxDateTime" structure and     */
  /* format it as a string with a leading decimal point */

  if (sxDateTime->useMicroseconds)
    {
    /* this code was merged from time_str2.c */
    /* number of microsecond digits changed from 5 to to 6 */

    sprintf (theFraction, ".%06ld", sxDateTime->microseconds);
    strcat (dest, theFraction);
    }

  /* now get the number of minutes stored as an integer */
  /* for time zone offset and place it in a string      */
  /* in the format of "+hh:mm" or "-hh:mm"              */

  if (sxDateTime->useTZ)
    {
    tzValue = sxDateTime->tz;
    /* there are 1440 minutes in a day */
    /* if tzValue is outsize this value +/- it is invalid */
    /* if so, we don't know the correct zone but will default to zero */

    /* we are allowing +/- 23:59 just to be tolerant, but usually only */
    /* only +/- 12 hours is actually used. */

    if ((tzValue >= 1440) || (tzValue <= -1440))
      {
      SLOGALWAYS1 ("ERROR: UtcValueToXmlString sxDateTime.tz value %d out of range",
          sxDateTime->tz);
      strcat (dest, "Z");    /* assume GMT in case our retcode is ignored */
      return SD_FAILURE;
      }

    if (tzValue == 0)
      /* output format:  "yyyy-mm-ddThh:mm:ss.fffffZ"           	*/
      strcat (dest, "Z");

    else
      {
      /* output format:  "yyyy-mm-ddThh:mm:ss.fffff+/-hh:mm"           */

      if (tzValue < 0)
        {
        tzSign = '-';
	tzValue = -tzValue;
        }

      sprintf (theTZ, "%c%02d:%02d", tzSign, tzValue / 60, tzValue % 60);
      strcat (dest, theTZ);
      }
    }

  return SD_SUCCESS;
  }				


/************************************************************************/
/* CalculateTimeZoneOffset                                              */
/************************************************************************/
/*  Figures out the difference between UTC/GMT/Zulu time and            */
/*  local time.                                                         */
/*  This difference can be used after the "mktime"                      */
/*  function is called. The "mktime" function returns local time        */
/*  and to convert this time to a UTC time this calculated              */
/*  adjustment must be added to the local time value returned           */
/*  from "mktime".                                                      */
/************************************************************************/

/* NOTE: this function may no longer be necessary */

ST_DOUBLE CalculateTimeZoneOffset (ST_VOID)
  {
time_t     currTime, local_t, utc_t;
struct tm *pJunkTm, localTm, utcTm;
ST_DOUBLE  timeZoneAdjustment;

  currTime = time (NULL);		          /* get the current system time */
  pJunkTm = gmtime (&currTime);		          /* convert current time value in seconds to a structure */
  memcpy (&utcTm, pJunkTm, sizeof (utcTm));	  /* save current UTC time */
  pJunkTm = localtime (&currTime);		  /* convert current time value and correct for local time zone */
  memcpy (&localTm, pJunkTm, sizeof (localTm));	  /* save local UTC time */
  utc_t = mktime (&utcTm);                        /* convert UTC time to UTC seconds */
  local_t = mktime (&localTm);			  /* convert local time to UTC seconds */
  timeZoneAdjustment = difftime (local_t, utc_t); /* find the difference or time zone offset */

  return (timeZoneAdjustment);
  }

/*   this code was merged from time_str2.c                              */

/************************************************************************/
/*   GetTimeAndUsec                                                     */
/************************************************************************/  
/* a function to supply current time as a time_t, and the number of     */
/* microseconds, in a single call.  this will simplify the setting of   */
/* data structures such as DateTime which have both of these values.    */
/************************************************************************/  

#if defined(_WIN32) || defined(linux) || defined(__QNX__)
time_t GetTimeAndUsec (long *usec)
  {
#ifdef _WIN32
  struct _timeb tb;
  _ftime (&tb);  
#else
  struct timeb  tb;
  ftime (&tb);
#endif

  if (usec != NULL)
    {
    *usec = ((long)tb.millitm) * 1000;
    }
  return tb.time;
  } 
#endif	/* some platforms	*/


/*****************************************************************************/
/*                          usr_mkgmtime_isleap                              */
/*  return 1 if 'year' is a leap year, else return 0                         */
/*****************************************************************************/

static time_t               usr_mkgmtime_isleap (time_t year)
  {
  if ((year < 0 ) || (year > 32767))
    {
    return 0;               /* assume invalid years are not leap years */
    }

  if ((year % 4000) == 0)
    {
    return 0;               /* multiples of 4000 are not leap years */
    }

  if ((year % 400) == 0)
    {
    return 1;               /* multiples of 400 are leap years */
    }

  if ((year % 100) == 0)
    {
    return 0;               /* multiples of 100 are not leap years */
    }

  if ((year % 4) == 0)
    {
    return 1;               /* multiples of 4 are leap years */
    }

  return 0;                 /* all others are not leap years */

  } /* usr_mkgmtime_isleap */


/*****************************************************************************/
/*                          usr_mkgmtime_leap_year_days                      */
/*  return number leap-year days based on 'year'                             */
/*****************************************************************************/

static time_t               usr_mkgmtime_leap_year_days (time_t year)
  {
  /* number of 4000-year multiples */
  time_t n4000 = (year / (time_t) 4000);

  /* number of 400-year multiples in excess of 4000 */
  time_t n400  = (year % (time_t) 4000) / (time_t) 400;

  /* number of 100-year multiples in excess of 400 */
  time_t n100  = (year % (time_t) 400) / (time_t) 100;

  /* number of 4-year multiples in excess of 100 */
  time_t n4    = (year % (time_t) 100) / (time_t) 4;

  return
    ( (time_t) 969 * n4000 )
  + ( (time_t)  97 * n400  )
  + ( (time_t)  24 * n100  )
  + ( (time_t)   1 * n4    );

  } /* usr_mkgmtime_leap_year_days */


/*****************************************************************************/
/*                          usr_mkgmtime                                     */
/*  a portable implementation of mkgmtime(), a GMT-based mktime() function.  */
/*                                                                           */
/*  note: unlike typical system implementations of mktime(), this function   */
/*  does NOT normalize the struct tm fields, which are required to be valid. */
/*                                                                           */
/*  for consistency, tm_isdst should be set to 0 upon entry (since GMT does  */
/*  not use daylight savings time), but the function ignores this field.     */
/*  (when tm_isdst is set to 0, the system mktime() api could be used to     */
/*  normalize the input fields prior to calling usr_mkgmtime, if necessary.) */
/*                                                                           */
/*  the output fields tm_wday and tm_yday are correctly set upon exit when   */
/*  the result time_t value is normal.                                       */
/*                                                                           */
/*  upon error, a value of (time_t) (-1) is returned, and the output fields  */ 
/*  tm_wday and tm_yday are not set in that case.  detected errors are: NULL */
/*  pointer to the struct tm, date/time values out of valid range, and any   */
/*  input that results in a negative time_t value.  note that the range of   */
/*  input values that could produce a negative time_t are dependent on the   */
/*  datatype of time_t, which could be 32 or 64-bit, and signed or unsigned  */
/*  on some platforms.                                                       */
/*****************************************************************************/


#define USR_MKGMTIME_BASE_DAY    719527


time_t                      usr_mkgmtime (
  struct tm *               t)
  {
  time_t                    w_time_t;
  time_t                    this_year, this_mon, this_day, leap, max_day;
  int                       wday;

  static int                usr_mkgmtime_days_per_mon [13] =
    {  0,
      31,   /* JAN */
      28,   /* FEB */
      31,   /* MAR */
      30,   /* APR */
      31,   /* MAY */
      30,   /* JUN */
      31,   /* JUL */
      31,   /* AUG */
      30,   /* SEP */
      31,   /* OCT */
      30,   /* NOV */
      31    /* DEC */
    };

  /* offset from start of year for a given month.          */
  /* to compensate for normal day numbers starting with 1, */
  /* we pre-subtract 1 to optimize the calculation.        */
  
  static int                usr_mkgmtime_mon_offset [13] =
    {  0,
       0-1, /* JAN */       /* so Jan 1 is day 0 */
      31-1, /* FEB */
      59-1, /* MAR */
      90-1, /* APR */
     120-1, /* MAY */
     151-1, /* JUN */
     181-1, /* JUL */
     212-1, /* AUG */
     243-1, /* SEP */
     273-1, /* OCT */
     304-1, /* NOV */
     334-1  /* DEC */
    };

  /* validate struct tm input values */

  if (t == NULL)
    {
    return (time_t) (-1);
    }

  this_year = t->tm_year + 1900;
  this_mon  = t->tm_mon  + 1;
  this_day  = t->tm_mday;

  if ( (this_year  < 1970)
  ||   (this_mon   <  1)
  ||   (this_mon   > 12)
  ||   (this_day   <  1)
  ||   (t->tm_hour <  0)
  ||   (t->tm_hour > 23)
  ||   (t->tm_min  <  0)
  ||   (t->tm_min  > 59)
  ||   (t->tm_sec  <  0)
  ||   (t->tm_sec  > 59) )
    {
    return (time_t) (-1);
    }

  leap = usr_mkgmtime_isleap (this_year);
  max_day = usr_mkgmtime_days_per_mon[this_mon];

  if (this_mon == (time_t) 2)
    {
    max_day += leap;
    }

  if (this_day > max_day)
    {
    return (time_t) (-1);
    }

  /* form number of days for given year.   we start by determining number */
  /* of leap-year days in the years prior to current one */

  w_time_t = (time_t) (((time_t) 365 * this_year) + 
             usr_mkgmtime_leap_year_days (this_year-1));

  /* convert year/mon to days using month-offset table */

  /* we must account for the fact that Jan 1 is day 0 of a year. */
  /* so, "day number" is one less than the number of days */
  /* by subtracting 1 from the day-offset of a given month */
  
  /* however, since this value will be constant for a given month, */
  /* the offset table already applies the '-1' factor, to save time. */
  /* see the comments above for the table's definition. */

  this_day += (time_t) usr_mkgmtime_mon_offset [this_mon];

  if (this_mon > (time_t) 2)
    {
    this_day += leap;    /* leap-year day's effect doesn't occur until March */
    }

  /* calculate base day of Jan 1 1970.  1970 was not a leap year, */
  /* so the number of leap-year days of 1970 is the same as 1969, */
  /* but to be consistent, we call the function using (1970-1).   */
  /* since 1,970 years go from the (theoretical) year 0 to 1969,  */
  /* the two parts of this equation are consistent.               */

  /* the following would calculate the base day value */

  /* however, since the values are all constants, the same base day will be  */
  /* produced each time.  running the equation with these constants produces */
  /* the number 719527, which we use in order to save time.                  */

  /* USR_MKGMTIME_BASE_DAY = (time_t) (1970 * 365)                           */
  /*                       + usr_mkgmtime_leap_year_days ((time_t) (1970-1)) */

  w_time_t += (time_t) (this_day - ((time_t) USR_MKGMTIME_BASE_DAY));

  /* create output fields: tm_wday and tm_yday */
  /* the unix epoch of 1970-01-01 was a Thursday, or tm_wday == 4, */

  /* tm_wday: Day of week (0 ?6; Sunday = 0) */
  /* tm_yday: Day of year (0 ?365; January 1 = 0) */

  wday = ((int) w_time_t + 4) % 7;

  w_time_t = (w_time_t * (time_t) 24) + (time_t) t->tm_hour;
  w_time_t = (w_time_t * (time_t) 60) + (time_t) t->tm_min;
  w_time_t = (w_time_t * (time_t) 60) + (time_t) t->tm_sec;

  if (w_time_t < (time_t) 0)
    {
    return (time_t) (-1);
    }

  /* store output fields after time_t has been validated */

  t->tm_wday = wday;
  t->tm_yday = (int) this_day;;

  return w_time_t;

  } /* usr_mkgmtime */
