/*	$Id$
 *	Author:	Roger A. Cole
 *	Date:	08-09-90
 *
 *	Experimental Physics and Industrial Control System (EPICS)
 *
 *	Copyright 1991-92, the Regents of the University of California,
 *	and the University of Chicago Board of Governors.
 *
 *	This software was produced under  U.S. Government contracts:
 *	(W-7405-ENG-36) at the Los Alamos National Laboratory,
 *	and (W-31-109-ENG-38) at Argonne National Laboratory.
 *
 *	Initial development by:
 *		The Controls and Automation Group (AT-8)
 *		Ground Test Accelerator
 *		Accelerator Technology Division
 *		Los Alamos National Laboratory
 *
 *	Co-developed with
 *		The Controls and Computing Group
 *		Accelerator Systems Division
 *		Advanced Photon Source
 *		Argonne National Laboratory
 *
 * Modification Log:
 * -----------------
 *  .00 08-09-90 rac	initial version
 *  .01 06-18-91 rac	installed in SCCS
 *  .02 07-20-91 rac	use EPICS_TS_MIN_WEST to override TS_MIN_WEST
 *  .03 08-16-91 rac	use envGetLongConfigParam
 *  .04 09-05-91 joh	updated for v5 vxWorks
 *  .05 01-27-92 rac	fixed off-by-1 bug for leap years in tsTextToStamp
 *  .06 08-03-92 rac	added tsRound... routines
 *  .07 10-06-92 rac	minor fixes to the documentation
 *  .08 04-02-92 joh 	fixed number of days in a year uninitialized
 *  .09 02-09-94 jbk 	tsLocalTime() now calls TScurrentTimeStamp() vxWorks
 *			for the EPOCH year
 *  .10	05-04-94 pg	HPUX cpp changes. (elif to else and if)
 *
 * make options
 *	-DvxWorks	makes a version for VxWorks
 *	-DNDEBUG	don't compile assert() checking
 *      -DDEBUG         compile various debug code, including checks on
 *                      malloc'd memory
 */
/*+/mod***********************************************************************
* TITLE	tsSubr.c - time stamp routines
*
* DESCRIPTION
*	This module contains routines relating to time stamps.  Capabilities
*	provided include: converting from time stamp to text; converting
*	from text to time stamp; computing the difference between two time
*	stamps; and getting system time in the form of a time stamp.
*
*	A time stamp is a pair of `unsigned long' quantities: the number
*	of seconds past an epoch; and the number of nanoseconds within
*	the second.  Time stamps always reference GMT.  Except as noted in
*	individual routines, the nanosecond part of the time stamp is assumed
*	always to be between 0 and 999999999; these routines always produce
*	time stamps which follow the rule.
*
*	These routines handle leap year issues and daylight savings time
*	issues, and properly handle dates after 1999.  The tsDefs.h header
*	file contains definitions which must be customized for the local
*	time zone.
*
* QUICK REFERENCE
* #include <tsDefs.h>
* TS_STAMP  timeStamp;
*  void  date(								)
*  void  tsAddDouble(      >pStampSum,    pStamp,        secAsDouble	)
*  void  TsAddDouble(      >pStampSum,    pStamp,        secAsDouble	)
*  void  TsAddStamps(      >pStampSum,    pStamp1,       pStamp2	)
*   int  tsCmpStamps(       pStamp1,      pStamp2			)
*   int  TsCmpStampsEQ(     pStamp1,      pStamp2			)
*   int  TsCmpStampsNE(     pStamp1,      pStamp2			)
*   int  TsCmpStampsGT(     pStamp1,      pStamp2			)
*   int  TsCmpStampsGE(     pStamp1,      pStamp2			)
*   int  TsCmpStampsLT(     pStamp1,      pStamp2			)
*   int  TsCmpStampsLE(     pStamp1,      pStamp2			)
*  void  TsDiffAsDouble(   >pSecAsDouble, pStamp1,       pStamp2	)
*  void  TsDiffAsStamp(    >pStampDiff,   pStamp1,       pStamp2	)
*  long  tsLocalTime(      >pStamp					)
*  long  tsRoundDownLocal(<>pStamp,       intervalUL			)
*  long  tsRoundUpLocal(  <>pStamp,       intervalUL			)
*  char *tsStampToText(     pStamp,       textType,      >textBuffer	)
*					 TS_TEXT_MONDDYYYY   buf[32]
*					 TS_TEXT_MMDDYY      buf[28]
*  long  tsTextToStamp(    >pStamp,    <>pTextPointer			)
*  long  tsTimeTextToStamp(>pStamp,    <>pTextPointer			)
*
* BUGS
* o	time stamps on the first day of the epoch are taken to be `delta
*	times', thus making it impossible to represent times on the first
*	day of the epoch as a time stamp.
*
* NOTES
* 1.	The environment variable EPICS_TS_MIN_WEST overrides the default
*	time zone established by TS_MIN_WEST in tsDefs.h
*
* SEE ALSO
*	tsDefs.h	contains definitions needed to use these routines,
*			as well as configuration information
*
* (Some of the code here is based somewhat on time-related routines from
* 4.2 BSD.)
*-***************************************************************************/

#ifdef vxWorks
#   include <vxWorks.h>
#   include <stdioLib.h>
#   include <strLib.h>
#   include <ctype.h>
#else
#if VMS
#else
#   include <sys/time.h>
#   include <stdio.h>
#   include <string.h>
#   include <strings.h>
#   include <ctype.h>
#endif
#endif

#ifndef INC_genDefs_h
#   include <genDefs.h>		/* provides assert() */
#endif
#include <envDefs.h>
#define TS_PRIVATE_DATA
#include <tsDefs.h>

static void tsStampFromLocal();
static void tsStampToLocal();
static void tsStampToLocalZone();
static void tsInitMinWest();

static int needToInitMinWest=1;
static long tsMinWest=TS_MIN_WEST;

static int daysInMonth[] =    {31,28,31,30, 31, 30, 31, 31, 30, 31, 30, 31};
static int dayYear1stOfMon[] = {0,31,59,90,120,151,181,212,243,273,304,334};
static char	monthText[] = "JanFebMarAprMayJunJulAugSepOctNovDec";


/*/subhead tsTest-------------------------------------------------------------
*    some test code.
*----------------------------------------------------------------------------*/
#if TS_TEST || LINT

#  ifndef vxWorks
  main()
  {
    return tsTest();
  }
#  endif
tsTest()
{
    long	stat;
    double	dbl;
    TS_STAMP	stamp1;
    char	stamp1Txt[32];
    TS_STAMP	stamp2;
    char	stamp2Txt[32];
    TS_STAMP	stamp3;
    char	stamp3Txt[32];
    TS_STAMP	stampD;
    char	stampDTxt[32];
    TS_STAMP	stampDbl;
    char	stampDblTxt[32];
    TS_STAMP	stampDblD;
    char	stampDblDTxt[32];
    TS_STAMP	inStamp;
    char	inStampTxt[80];

/*----------------------------------------------------------------------------
*    get current date and print in the various ways possible
*----------------------------------------------------------------------------*/
    (void)printf("Getting current date:\n");
    if ((stat = tsLocalTime(&stamp1)) != S_ts_OK) {
	(void)printf("%s\n", TsStatusToText(stat));
	return 1;
    }
    (void)printf("	%s\n",
			tsStampToText(&stamp1, TS_TEXT_MONDDYYYY, stamp1Txt));
    (void)printf("	%s\n",
			tsStampToText(&stamp1, TS_TEXT_MMDDYY, stamp1Txt));

/*----------------------------------------------------------------------------
*    now, play around a bit with other routines
*----------------------------------------------------------------------------*/
    stamp2.secPastEpoch = 80432;
    stamp2.nsec = 900000000;

    TsAddStamps(&stamp3, &stamp1, &stamp2);
    (void)printf("%s plus %s is %s\n",
			stamp1Txt,
			tsStampToText(&stamp2, TS_TEXT_MMDDYY, stamp2Txt),
    			tsStampToText(&stamp3, TS_TEXT_MMDDYY, stamp3Txt));
    TsDiffAsStamp(&stampD, &stamp3, &stamp1);
    (void)printf("%s minus %s is %s\n",
			stamp3Txt, stamp1Txt,
    			tsStampToText(&stampD, TS_TEXT_MMDDYY, stampDTxt));
    TsDiffAsDouble(&dbl, &stamp3, &stamp1);
    (void)printf("%s Minus %s is %.9f\n", stamp3Txt, stamp1Txt, dbl);
    TsDiffAsDouble(&dbl, &stamp1, &stamp3);
    (void)printf("%s Minus %s is %.9f\n", stamp1Txt, stamp3Txt, dbl);

    tsAddDouble(&stampDbl, &stamp1, 80432.9);
    (void)printf("%s plus %.9f is %s\n",
			stamp1Txt, 80432.9,
    			tsStampToText(&stampDbl, TS_TEXT_MMDDYY, stampDblTxt));
    TsAddDouble(&stampDbl, &stamp1, 80432.9);
    (void)printf("%s Plus %.9f is %s\n",
			stamp1Txt, 80432.9,
    			tsStampToText(&stampDbl, TS_TEXT_MMDDYY, stampDblTxt));
    tsAddDouble(&stampDblD, &stampDbl, -80432.9);
    (void)printf("%s plus %.9f is %s\n",
			stampDblTxt, -80432.9,
    			tsStampToText(&stampDblD,TS_TEXT_MMDDYY, stampDblDTxt));
    TsAddDouble(&stampDblD, &stampDbl, -80432.9);
    (void)printf("%s Plus %.9f is %s\n",
			stampDblTxt, -80432.9,
    			tsStampToText(&stampDblD,TS_TEXT_MMDDYY, stampDblDTxt));

    stamp2.nsec = 100000000;
    TsAddStamps(&stamp3, &stamp1, &stamp2);
    (void)printf("%s plus %s is %s\n",
			stamp1Txt,
			tsStampToText(&stamp2, TS_TEXT_MMDDYY, stamp2Txt),
    			tsStampToText(&stamp3, TS_TEXT_MMDDYY, stamp3Txt));
    TsDiffAsStamp(&stampD, &stamp3, &stamp1);
    (void)printf("%s minus %s is %s\n",
			stamp3Txt, stamp1Txt,
    			tsStampToText(&stampD, TS_TEXT_MMDDYY, stampDTxt));
    TsDiffAsDouble(&dbl, &stamp3, &stamp1);
    (void)printf("%s Minus %s is %.9f\n", stamp3Txt, stamp1Txt, dbl);
    TsDiffAsDouble(&dbl, &stamp1, &stamp3);
    (void)printf("%s Minus %s is %.9f\n", stamp1Txt, stamp3Txt, dbl);

    tsAddDouble(&stampDbl, &stamp1, 80432.1);
    (void)printf("%s plus %.9f is %s\n",
			stamp1Txt, 80432.1,
    			tsStampToText(&stampDbl, TS_TEXT_MMDDYY, stampDblTxt));
    TsAddDouble(&stampDbl, &stamp1, 80432.1);
    (void)printf("%s Plus %.9f is %s\n",
			stamp1Txt, 80432.1,
    			tsStampToText(&stampDbl, TS_TEXT_MMDDYY, stampDblTxt));
    tsAddDouble(&stampDblD, &stampDbl, -80432.1);
    (void)printf("%s plus %.9f is %s\n",
			stampDblTxt, -80432.1,
    			tsStampToText(&stampDblD,TS_TEXT_MMDDYY, stampDblDTxt));
    TsAddDouble(&stampDblD, &stampDbl, -80432.1);
    (void)printf("%s Plus %.9f is %s\n",
			stampDblTxt, -80432.1,
    			tsStampToText(&stampDblD,TS_TEXT_MMDDYY, stampDblDTxt));

    (void)printf("comparisons for %s vs %s\n", stamp1Txt, stamp3Txt);
    (void)printf("	tsCmpStamps:  A,B %2d    B,A %2d    A,A %2d\n",
	    tsCmpStamps(&stamp1, &stamp3), tsCmpStamps(&stamp3, &stamp1),
	    tsCmpStamps(&stamp1, &stamp1));
    (void)printf(
	"	macro A vs B: EQ %d  NE %d  GT %d  GE %d  LT %d  LE %d\n",
	    TsCmpStampsEQ(&stamp1, &stamp3), TsCmpStampsNE(&stamp1, &stamp3),
	    TsCmpStampsGT(&stamp1, &stamp3), TsCmpStampsGE(&stamp1, &stamp3),
	    TsCmpStampsLT(&stamp1, &stamp3), TsCmpStampsLE(&stamp1, &stamp3));
    (void)printf(
	"	macro B vs A: EQ %d  NE %d  GT %d  GE %d  LT %d  LE %d\n",
	    TsCmpStampsEQ(&stamp3, &stamp1), TsCmpStampsNE(&stamp3, &stamp1),
	    TsCmpStampsGT(&stamp3, &stamp1), TsCmpStampsGE(&stamp3, &stamp1),
	    TsCmpStampsLT(&stamp3, &stamp1), TsCmpStampsLE(&stamp3, &stamp1));
    (void)printf(
	"	macro A vs A: EQ %d  NE %d  GT %d  GE %d  LT %d  LE %d\n",
	    TsCmpStampsEQ(&stamp1, &stamp1), TsCmpStampsNE(&stamp1, &stamp1),
	    TsCmpStampsGT(&stamp1, &stamp1), TsCmpStampsGE(&stamp1, &stamp1),
	    TsCmpStampsLT(&stamp1, &stamp1), TsCmpStampsLE(&stamp1, &stamp1));

    stamp2.secPastEpoch = 0;
    stamp2.nsec = 10000000;
    TsAddStamps(&stamp3, &stamp1, &stamp2);
    (void)printf("comparisons for %s vs %s\n", stamp1Txt,
    			tsStampToText(&stamp3, TS_TEXT_MMDDYY, stamp3Txt));
    (void)printf("	tsCmpStamps:  A,B %2d    B,A %2d    A,A %2d\n",
	    tsCmpStamps(&stamp1, &stamp3), tsCmpStamps(&stamp3, &stamp1),
	    tsCmpStamps(&stamp1, &stamp1));
    (void)printf(
	"	macro A vs B: EQ %d  NE %d  GT %d  GE %d  LT %d  LE %d\n",
	    TsCmpStampsEQ(&stamp1, &stamp3), TsCmpStampsNE(&stamp1, &stamp3),
	    TsCmpStampsGT(&stamp1, &stamp3), TsCmpStampsGE(&stamp1, &stamp3),
	    TsCmpStampsLT(&stamp1, &stamp3), TsCmpStampsLE(&stamp1, &stamp3));
    (void)printf(
	"	macro B vs A: EQ %d  NE %d  GT %d  GE %d  LT %d  LE %d\n",
	    TsCmpStampsEQ(&stamp3, &stamp1), TsCmpStampsNE(&stamp3, &stamp1),
	    TsCmpStampsGT(&stamp3, &stamp1), TsCmpStampsGE(&stamp3, &stamp1),
	    TsCmpStampsLT(&stamp3, &stamp1), TsCmpStampsLE(&stamp3, &stamp1));

/*----------------------------------------------------------------------------
*    exercise tsTextToStamp
*----------------------------------------------------------------------------*/
    inStampTxt[0] = '\0';
    while (strncmp(inStampTxt, "quit", 4) != 0) {
	(void)printf("type text date/time or quit: ");
	if (fgets(inStampTxt, 80, stdin) == NULL) {
	    (void)strcpy(inStampTxt, "quit");
	    (void)printf("\n");
	    clearerr(stdin);
	}
	else if (strncmp(inStampTxt, "quit", 4) == 0)
	    ;		/* no action */
	else {
	    char	*pText;
	    pText = inStampTxt;
	    if ((stat = tsTextToStamp(&inStamp, &pText)) != S_ts_OK) {
		(void)printf("%s\n", TsStatusToText(stat));
	    }
	    else {
		(void)printf("stamp is: %lu %lu %s\n", inStamp.secPastEpoch,
			inStamp.nsec,
			tsStampToText(&inStamp, TS_TEXT_MMDDYY, inStampTxt));
	    }
	}
    }

    clearerr(stdin);
    return 0;
}
#endif

#ifdef vxWorks
/*+/subr**********************************************************************
* NAME	date - print present time and date
*
* DESCRIPTION
*	Prints the present date and time when running under VxWorks.
*
* RETURNS
*	void
*
*-*/
void
date()
{
    TS_STAMP	now;
    char	nowText[32];

    (void)tsLocalTime(&now);
    (void)printf("%s\n", tsStampToText(&now, TS_TEXT_MONDDYYYY, nowText));
}
#endif

/*+/internal******************************************************************
* NAME	sunday - find closest Sunday
*
* DESCRIPTION
*	Finds the day of the year for the closest Sunday to the specified
*	day of the year; caller can specify whether the preceding or
*	following Sunday is desired.
*
* RETURNS
*	day of year for the desired Sunday
*
*-*/
static
sunday(day, leap, dayYear, dayOfWeek)
int	day;		/* I day of year to find closest Sunday */
int	leap;		/* I 0, 1 for not leap year, leap year, respectively */
int	dayYear;	/* I known day of year */
int	dayOfWeek;	/* I day of week for dayYear */
{
    int		offset;		/* controls direction of offset */

/*----------------------------------------------------------------------------
*    The principle of this routine is: if I know the day of the week for a
*    particular day in this year, then the day of the week for the caller's
*    day is (myDOW - (myDay - callersDay) % 7)  .  (This needs to be
*    elaborated a little, since it can generate numbers outside 0 to 6  .)
*    Once the day of the week is known for the caller's day, the previous
*    Sunday is obtained by subtracting the caller's day of week from the
*    caller's day; if the following Sunday is desired, then finish up by
*    adding 7 to the date for the prior Sunday.
*
*    'offset' takes care of the 'outside 0 to 6' issue and also takes care
*    of the direction for 'prior' or 'following'.  There isn't any magic
*    about the choice of 700 as the value--it must be a multiple of 7 and
*    must be larger than the number of days in a year.
*----------------------------------------------------------------------------*/
    if (day < 0) {	/* look for prior Sunday */
	offset = -700;
	day = -day;
	assert(day > 5);	/* day can't be too early in year */
    }
    else {		/* look for following Sunday */
	offset = 700;
	assert(day < 360);	/* day can't be too late in year */
    }
/*----------------------------------------------------------------------------
*    adjust caller's day of year to compensate for leap year.  If the day is
*    after Feb 28, add 1 in leap years.  If the day is Feb 28, then also add
*    1, so that the day stays "last day in Feb".
*----------------------------------------------------------------------------*/
    if (day >= 58)
	day += leap;

    return (day - (day - dayYear + dayOfWeek + offset) % 7 );
}

/*+/macro*********************************************************************
* NAME	TsAddXxx - addition for time stamps
*
* DESCRIPTION
*	These routines and macros produce a time stamp which is the sum of a
*	time stamp and another quantity.  For xxAddDouble(), the `other
*	quantity' is a positive or negative double precision value.  For
*	TsAddStamps, the `other quantity' is another time stamp.
*
*	    tsAddDouble(pStampSum, pStamp1, secAsDouble)	function
*	    TsAddDouble(pStampSum, pStamp1, secAsDouble)	macro
*	    TsAddStamps(pStampSum, pStamp1, pStamp2)		macro
*
*	pStampSum can be the same as one of the other pStamp arguments.
*
* RETURNS
*	void
*
* NOTES
* 1.	These tools properly handle the case where pStamp1->nsec and/or
*	pStamp2->nsec are greater then 1000000000, so long as the sum is
*	less than 4294967295 (i.e., 0xffffffff).
* 2.	For xxAddDouble(), if secAsDouble is negative, its absolute value
*	is assumed to be smaller than pStamp, so that the sum will be positive.
* 3.	Using xxAddDouble() can produce time stamps whose resolution differs
*	from the resolution of the addend time stamp; this can be confusing
*	for comparisons and for tsStampToText().  For example,
*	9/10/90 10:23:06.940 plus 80432.9 (equivalent to 22:20:32.900)
*	produces 9/11/90 8:43:39.839999999 rather than ...840, as desired.
*
* EXAMPLE
* 1.	Compute the time stamp corresponding to 2 hours past the present
*	time and date.
*
*	TS_STAMP	now, later;
*	double		twoHours=7200.;
*
*	tsLocalTime(&now);
*	TsAddDouble(&later, &now, twoHours);
*
*-*/
void
tsAddDouble(pSum, pStamp, dbl)
TS_STAMP *pSum;		/* O sum time stamp */
TS_STAMP *pStamp;	/* I addend time stamp */
double	dbl;		/* I number of seconds to add */
{
    TS_STAMP	stamp;		/* stamp equivalent of the double */

    if (dbl >= 0.) {
	stamp.secPastEpoch = (unsigned long)dbl;
	stamp.nsec = (unsigned long)
			((dbl - (double)stamp.secPastEpoch) * 1000000000.);
	TsAddStamps(pSum, pStamp, &stamp);
    }
    else {
	dbl = -dbl;
	stamp.secPastEpoch = (unsigned long)dbl;
	stamp.nsec = (unsigned long)
			((dbl - (double)stamp.secPastEpoch) * 1000000000.);
	TsDiffAsStamp(pSum, pStamp, &stamp);
    }
}

/*+/subr**********************************************************************
* NAME	tsCmpStamps - compare two time stamps
*
* DESCRIPTION
*	Compare two time stamps and return (similar to strcmp() ) negative,
*	zero, or positive if the first time stamp is, respectively, less
*	than, equal to, or greater than the second.
*
*	Several comparison macros are also available in tsDefs.h  .  These
*	macros return a true condition if the comparison test succeeds:
*
*	    TsCmpStampsEQ(pStamp1, pStamp2) is true if stamp1 == stamp2
*	    TsCmpStampsNE(pStamp1, pStamp2) is true if stamp1 != stamp2
*	    TsCmpStampsGT(pStamp1, pStamp2) is true if stamp1 >  stamp2
*	    TsCmpStampsGE(pStamp1, pStamp2) is true if stamp1 >= stamp2
*	    TsCmpStampsLT(pStamp1, pStamp2) is true if stamp1 <  stamp2
*	    TsCmpStampsLE(pStamp1, pStamp2) is true if stamp1 <= stamp2
*
* RETURNS
*	see description above
*
* EXAMPLES
* 1.	Compare two time stamps.
*	TS_STAMP	stamp1, stamp2;
*
*	if ((i=tsCmpStamps(&stamp1, &stamp2)) == 0)
*	    printf("stamps are equal\n");
*	else if (i < 0)
*	    printf("first stamp is earlier\n");
*	else
*	    printf("first stamp is later\n");
*
* 2.	Compare two time stamps.
*	TS_STAMP	stamp1, stamp2;
*
*	if (TsCmpStampsEQ(&stamp1, &stamp2))
*	    printf("stamps are equal\n");
*	else if (TsCmpStampsLT(&stamp1, &stamp2))
*	    printf("first stamp is earlier\n");
*	else
*	    printf("first stamp is later\n");
*
*-*/
int
tsCmpStamps(pStamp1, pStamp2)
TS_STAMP *pStamp1;	/* pointer to first stamp */
TS_STAMP *pStamp2;	/* pointer to second stamp */
{
    if (pStamp1->secPastEpoch < pStamp2->secPastEpoch)
	return -1;
    else if (pStamp1->secPastEpoch == pStamp2->secPastEpoch) {
	if (pStamp1->nsec < pStamp2->nsec)
	    return -1;
	else if (pStamp1->nsec == pStamp2->nsec)
	    return 0;
	else
	    return 1;
    }
    else
	return 1;
}

/*+/macro*********************************************************************
* NAME	TsDiffAsXxx - difference between two time stamps
*
* DESCRIPTION
*	These macros produce the result of subtracting two
*	time stamps, i.e.,
*
*		result = *pStamp1 - *pStamp2;
*
*	The form the result depends on the macro used.  For TsDiffAsDouble(),
*	the result is floating point seconds, represented as double.  For
*	TsDiffAsStamp(), the result is a time stamp.
*
*	    TsDiffAsDouble(pSecAsDouble, pStamp1, pStamp2)
*	    TsDiffAsStamp(pStampDiff, pStamp1, pStamp2)
*
* RETURNS
*	void
*
* NOTES
* 1.	Because time stamps are represented as unsigned quantities, care
*	must be taken that *pStamp1 is >= *pStamp2 for TsDiffAsStamp().  (For
*	TsDiffAsDouble(), this restriction doesn't apply.)
*
* EXAMPLES
* 1.	Compute the difference in seconds between two time stamps.
*	TS_STAMP	stamp1, stamp2;
*
*	printf("difference is %f sec.\n", TsDiffAsDouble(&stamp1, &stamp2);
*
*-*/

/*+/subr**********************************************************************
* NAME	tsLocalTime - get local time as a time stamp
*
* DESCRIPTION
*	The local system time is obtained, converted to the reference time
*	used by time stamps, and then converted into the form used for
*	time stamps.
*
* RETURNS
*	S_ts_OK, or
*	S_ts_sysTimeError	if error occurred getting time of day
*
* BUGS
* o	For SunOS, local system time is truncated at milli-seconds--i.e.,
*	micro-seconds are ignored.
*
* EXAMPLES
* 1.	Get the local time as a time stamp.
*	TS_STAMP	now;
*
*	tsLocalTime(&now);
*
*-*/
long
tsLocalTime(pStamp)
TS_STAMP *pStamp;	/* O pointer to time stamp buffer */
{
    long	retStat=S_ts_OK;/* return status to caller */

#ifdef vxWorks
#  if 0	/* 'test' code, for ioc core without time of day service */
    assert(pStamp != NULL);
    pStamp->nsec = 987000000;
    pStamp->secPastEpoch = 30 * 86400 + 495;	/* 0815 Jan 31 of epoch year */
#  else
	return TScurrentTimeStamp((struct timespec*)pStamp);
#  endif
#else
#if VMS
    assert(0);		/* not available on VMS */
    assert(pStamp != NULL);
#else		/* SunOS */
    struct timeval curtime;

    assert(pStamp != NULL);
    if (gettimeofday(&curtime, (struct timezone *)NULL) == -1)
	retStat = S_ts_sysTimeError;
    else {
	pStamp->nsec = ( curtime.tv_usec/1000 ) * 1000000;
	pStamp->secPastEpoch = curtime.tv_sec - TS_EPOCH_SEC_PAST_1970;
    }
#endif
#endif

    pStamp->nsec = pStamp->nsec - (pStamp->nsec % TS_TRUNC);
    return retStat;
}

/*+/subr**********************************************************************
* NAME	tsRoundDownLocal - round stamp down to interval, based on local time
*
* DESCRIPTION
*	The time stamp is rounded down to the prior interval, based on
*	local time.  The rounding interval must be between 1 second and
*	86400 seconds (a full day).
*
* RETURNS
*	S_ts_OK, or
*	S_ts_badRoundInterval if the rounding interval is invalid
*
* EXAMPLES
* 1.	Round a time stamp back to 0000 hours, at the beginning of the day.
*	TS_STAMP	now;
*
*	tsRoundDownLocal(&now, 86400);
*
*-*/
long
tsRoundDownLocal(pStamp, interval)
TS_STAMP *pStamp;	/* IO pointer to time stamp buffer */
unsigned long interval;	/* I rounding interval, in seconds */
{
    long	retStat=S_ts_OK;/* return status to caller */
    struct tsDetail detail;
    unsigned long seconds;

    tsStampToLocal(*pStamp, &detail);
    if (interval < 1. || interval > 86400.)
	retStat = S_ts_badRoundInterval;
    else {
	seconds = detail.seconds + 60*detail.minutes + 3600*detail.hours;
	seconds -= seconds % interval;
	detail.hours = seconds / 3600;
	detail.minutes = (seconds % 3600) / 60;
	detail.seconds = seconds % 60;
	tsStampFromLocal(pStamp, &detail);
	pStamp->nsec = 0;
    }

    return retStat;
}

/*+/subr**********************************************************************
* NAME	tsRoundUpLocal - round stamp up to interval, based on local time
*
* DESCRIPTION
*	The time stamp is rounded up to the following interval, based on
*	local time.  The rounding interval must be between 1 second and
*	86400 seconds (a full day).
*
* RETURNS
*	S_ts_OK, or
*	S_ts_badRoundInterval if the rounding interval is invalid
*
* EXAMPLES
* 1.	Round a time stamp forward to 0000 hours, at the beginning of the 
*	following day.
*	TS_STAMP	now;
*
*	tsRoundUpLocal(&now, 86400);
*
*-*/
long
tsRoundUpLocal(pStamp, interval)
TS_STAMP *pStamp;	/* IO pointer to time stamp buffer */
unsigned long interval;	/* I rounding interval, in seconds */
{
    long	retStat=S_ts_OK;/* return status to caller */
    struct tsDetail detail;
    unsigned long seconds;

    tsStampToLocal(*pStamp, &detail);
    if (interval < 1. || interval > 86400.)
	retStat = S_ts_badRoundInterval;
    else {
	seconds = detail.seconds + 60*detail.minutes + 3600*detail.hours;
	if (pStamp->nsec > 0)
	    seconds++;
	seconds += interval - 1;
	seconds -= seconds % interval;
	detail.hours = seconds / 3600;
	detail.minutes = (seconds % 3600) / 60;
	detail.seconds = seconds % 60;
	tsStampFromLocal(pStamp, &detail);
	pStamp->nsec = 0;
    }

    return retStat;
}

/*+/internal******************************************************************
* NAME	tsStampFromLocal - convert time stamp to local time
*
* DESCRIPTION
*	Converts a tsDetail structure for local time into an EPICS time
*	stamp, taking daylight savings time into consideration.
*
* RETURNS
*	void
*
* BUGS
* o	doesn't handle 0 time stamps for time zones west of Greenwich
*
*-*/
static void
tsStampFromLocal(pStamp, pT)
TS_STAMP	*pStamp;/* O EPICS time stamp resulting from conversion */
struct tsDetail *pT;	/* I pointer to time structure to convert */
{
    long	retStat=S_ts_OK;/* return status to caller */

    TS_STAMP	stamp;		/* temp for building time stamp */
    int		year;		/* temp for year number */
    int		days;		/* temp for days since epoch */
    int		dstBegin;	/* day DST begins */
    int		dstEnd;		/* day DST ends */
    int		dst;		/* (0, 1) for DST (isn't, is) in effect */

    if (needToInitMinWest)
	tsInitMinWest();

    year = TS_EPOCH_YEAR;
    days = pT->dayYear;
    while (year < pT->year) {
	if (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0))
	    days += 366;
	else
	    days += 365;
	year++;
    }
    pT->dayOfWeek = (days + TS_EPOCH_WDAY_NUM) % 7;
    stamp.secPastEpoch = days * 86400;	/* converted to seconds */
    stamp.secPastEpoch += pT->hours*3600 + pT->minutes*60 + pT->seconds;
/*----------------------------------------------------------------------------
*	we now have a stamp which corresponds to the text time, BUT with
*	the assumption that the text time is standard time.  Three daylight
*	time issues must be dealt with: was the text time during DST; was
*	the text time during the `skipped' time when standard ends and DST
*	begins; and was it during the `limbo' time when DST ends and standard
*	begins.
*
*	If, for example, DST ends at 2 a.m., and 1 hour is added during DST,
*	then the limbo time begins at 1 a.m. DST and ends at 2 a.m. standard.
*----------------------------------------------------------------------------*/
    dstBegin = sunday(TS_DST_BEGIN, pT->leapYear, pT->dayYear, pT->dayOfWeek);
    dstEnd = sunday(TS_DST_END, pT->leapYear, pT->dayYear, pT->dayOfWeek);
    assert(dstBegin != dstEnd);
    dst = 0;
    if (dstBegin < dstEnd && (pT->dayYear < dstBegin || pT->dayYear > dstEnd))
	    ;		/* not DST; no action */
    else if (dstBegin > dstEnd && pT->dayYear < dstBegin && pT->dayYear>dstEnd)
	    ;		/* not DST; no action */
    else if (pT->dayYear == dstBegin) {
	if (pT->hours < TS_DST_HOUR_ON)
	    ;		/* not DST; no action */
	else if (pT->hours < TS_DST_HOUR_ON + TS_DST_HRS_ADD)
	    retStat = S_ts_timeSkippedDST;
	else
	    dst = 1;
    }
    else if (pT->dayYear != dstEnd)
	dst = 1;
    else {
	if (pT->hours >= TS_DST_HOUR_OFF + TS_DST_HRS_ADD)
	    ;	/* not DST */
	else if (pT->hours < TS_DST_HOUR_OFF)
	    dst = 1;
	else if (pT->dstOverlapChar == 'd')
	    dst = 1;
    }
    if (dst)
	stamp.secPastEpoch -= TS_DST_HRS_ADD * 3600;
    stamp.secPastEpoch += tsMinWest*60;
    *pStamp = stamp;
}

/*+/internal******************************************************************
* NAME	tsStampToLocal - convert time stamp to local time
*
* DESCRIPTION
*	Converts an EPICS time stamp into a tsDetail structure for local
*	time, taking daylight savings time into consideration.
*
* RETURNS
*	void
*
* BUGS
* o	doesn't handle 0 time stamps for time zones west of Greenwich
*
*-*/
static void
tsStampToLocal(stamp, pT)
TS_STAMP	stamp;	/* I EPICS time stamp to convert */
struct tsDetail *pT;	/* O pointer to time structure for conversion */
{
    int		dstBegin;	/* day DST begins */
    int		dstEnd;		/* day DST ends */
    int		dst;		/* (0, 1) for DST (isn't, is) in effect */

/*----------------------------------------------------------------------------
*    convert stamp to local time.  Since no dst compensation has been
*    done, this is local STANDARD time.  All dst compensation uses standard
*    time for the tests and adjustments.
*----------------------------------------------------------------------------*/
    tsStampToLocalZone(&stamp, pT);

    dstBegin = sunday(TS_DST_BEGIN, pT->leapYear, pT->dayYear, pT->dayOfWeek);
    dstEnd = sunday(TS_DST_END, pT->leapYear, pT->dayYear, pT->dayOfWeek);
    dst = 0;
    pT->dstOverlapChar = ':';
    if (dstBegin < dstEnd && (pT->dayYear < dstBegin || pT->dayYear > dstEnd))
	    ;		/* not DST; no action */
    else if (dstBegin > dstEnd && pT->dayYear < dstBegin && pT->dayYear>dstEnd)
	    ;		/* not DST; no action */
    else if (pT->dayYear == dstBegin && pT->hours < TS_DST_HOUR_ON)
	;		/* not DST; no action */
    else if (pT->dayYear != dstEnd)
	dst = 1;
    else {
	if (pT->hours >= TS_DST_HOUR_OFF) {
	    ;	/* not DST; may be 'limbo' time */
	    if (pT->hours < TS_DST_HOUR_OFF + TS_DST_HRS_ADD)
		pT->dstOverlapChar = 's';/* standard time, in 'limbo' */
	}
	else if (pT->hours >= TS_DST_HOUR_OFF - TS_DST_HRS_ADD) {
	    dst = 1;
	    pT->dstOverlapChar = 'd';/* daylight time, in 'limbo' */
	}
	else
	    dst = 1;
    }

/*----------------------------------------------------------------------------
*    now, if necessary, change the time stamp to daylight and then convert
*    the resultant stamp to local time.
*----------------------------------------------------------------------------*/
    if (dst) {
	stamp.secPastEpoch += TS_DST_HRS_ADD * 3600;
	tsStampToLocalZone(&stamp, pT);
    }

    return;
}

static void
tsInitMinWest()
{
    int		error=0;

    if (envGetLongConfigParam(&EPICS_TS_MIN_WEST, &tsMinWest) != 0)
	error = 1;
    else
	if (tsMinWest > 1380 && tsMinWest < -1380)
	    error = 1;
    if (error) {
	    (void)printf(
"tsSubr: illegal value for %s\n\
tsSubr: default value of %d will be used\n",
EPICS_TS_MIN_WEST.name, tsMinWest);
    }
    needToInitMinWest = 0;
}

static void
tsStampToLocalZone(pStamp, pT)
TS_STAMP	*pStamp;/* pointer to EPICS time stamp to convert */
struct tsDetail *pT;	/* pointer to time structure for conversion */
{
    int		ndays;		/* number of days in this month or year */
    unsigned long secPastEpoch;	/* time from stamp, in local zone */
    int		days;		/* temp for count of days */
    int		hms;		/* left over hours, min, sec in stamp */

    if (needToInitMinWest)
	tsInitMinWest();

    assert(pStamp != NULL);
    assert(pStamp->secPastEpoch >= tsMinWest * 60);
    assert(pT != NULL);

/*----------------------------------------------------------------------------
*    move the time stamp from GMT to local time, then break it down into its
*    component parts
*----------------------------------------------------------------------------*/
    secPastEpoch = pStamp->secPastEpoch - tsMinWest * 60;
    hms = secPastEpoch % 86400;
    days = secPastEpoch / 86400;
    pT->seconds = hms % 60;
    pT->minutes = (hms/60) % 60;
    pT->hours = hms / 3600;
    pT->dayOfWeek = (days + TS_EPOCH_WDAY_NUM) % 7;

    pT->year=TS_EPOCH_YEAR;
    if (pT->year % 400 == 0 || (pT->year % 4 == 0 && pT->year % 100 != 0))
        ndays = 366;
    else
        ndays = 365;
    while (days > 0) {
	if (pT->year % 400 == 0 || (pT->year % 4 == 0 && pT->year % 100 != 0))
	    ndays = 366;
	else
	    ndays = 365;
	if (days >= ndays)
	    pT->year++;
	days -= ndays;
    }
    if (days < 0)
	days += ndays;
    if (ndays == 366)
	pT->leapYear = 1;
    else
	pT->leapYear = 0;
    pT->dayYear = days;

    pT->monthNum = 0;
    pT->dayMonth = days;
    while (pT->dayMonth > 0) {
	ndays = daysInMonth[pT->monthNum];
	if (pT->monthNum==1 && pT->leapYear)
	    ndays++;
	if (pT->dayMonth >= ndays)
	    pT->monthNum++;
	pT->dayMonth -= ndays;
    }
    if (pT->dayMonth < 0)
	pT->dayMonth += ndays;

    return;
}

/*+/subr**********************************************************************
* NAME	tsStampToText - convert a time stamp to text
*
* DESCRIPTION
*	A EPICS standard time stamp is converted to text.  The text
*	contains the time stamp's representation in the local time zone,
*	taking daylight savings time into account.
*
*	The required size of the caller's text buffer depends on the type
*	of conversion being requested.  The conversion types, buffer sizes,
*	and conversion formats are:
*
*	TS_TEXT_MONDDYYYY  32	Mon dd, yyyy hh:mm:ss.nano-secs
*	TS_TEXT_MMDDYY	   28	mm/dd/yy hh:mm:ss.nano-secs
*
*	If the time in the time stamp is less than or equal to the number
*	of seconds in the day, then the time stamp is assumed to be a
*	delta time.  In this case, only time is stored in the buffer,
*	omitting the date.
*
*	For any of the formats, the fractional part of the time (represented
*	as `nano-secs' above) is variable length.  If the usec and nsec part
*	of the time are both zero, only milli-seconds will be printed; or
*	else if the nsec part is zero, only milli-seconds and micro-seconds
*	will be printed.
*
*	During the period when "time repeats itself" at the switch from
*	daylight time to standard time, the ':' following the "hh" will be
*	replaced with either 'd' or 's' before or after the switch,
*	respectively.
*
* RETURNS
*	pointer to buffer
*
* NOTES
* 1.	There were several motivations for writing this routine:  First, the
*	UNIX routine wasn't particularly portable to VxWorks.  Second, the
*	UNIX routine wasn't re-entrant.  And, last, the UNIX routine didn't
*	provide special handling for "time repeating itself".
*
* EXAMPLES
* 1.	Get the local time as a time stamp and print it.
*	TS_STAMP	now;
*	char		nowText[28];
*
*	tsLocalTime(&now);
*	printf("time is %s\n", tsStampToText(&now, TS_TEXT_MMDDYY, nowText));
*
*-*/
char *
tsStampToText(pStamp, textType, textBuffer)
TS_STAMP *pStamp;	/* I pointer to time stamp */
enum tsTextType textType;/* I type of conversion desired; one of TS_TEXT_xxx */
char	*textBuffer;	/* O buffer to receive text */
{

    struct tsDetail t;		/* detailed breakdown of time stamp */
    char	fracPart[10];	/* fractional part of time */

    assert(textBuffer != NULL);

    (void)sprintf(fracPart, "%09d", pStamp->nsec);
    if (pStamp->nsec % 1000000 == 0)
	fracPart[3] = '\0';
    else if (pStamp->nsec % 1000 == 0)
	fracPart[6] = '\0';

    if (pStamp->secPastEpoch <= 86400) {
	t.hours = pStamp->secPastEpoch / 3600;
	t.minutes = (pStamp->secPastEpoch / 60) % 60;
	t.seconds = pStamp->secPastEpoch % 60;
	(void)sprintf(textBuffer, "%02d:%02d:%02d.%s",
    					t.hours, t.minutes, t.seconds,
					fracPart);
    }
    else {
	tsStampToLocal(*pStamp, &t);

	switch (textType) {
	    case TS_TEXT_MMDDYY:
		(void)sprintf(textBuffer, "%02d/%02d/%02d %02d%c%02d:%02d.%s",
					t.monthNum+1, t.dayMonth+1, t.year%100,
    					t.hours, t.dstOverlapChar, t.minutes,
					t.seconds, fracPart);
		break;
	    case TS_TEXT_MONDDYYYY:
		(void)sprintf(textBuffer, "%3.3s %02d, %4d %02d%c%02d:%02d.%s",
					&monthText[t.monthNum*3], t.dayMonth+1,
					t.year,
    					t.hours, t.dstOverlapChar, t.minutes,
					t.seconds, fracPart);
		break;
	    default:
		return NULL;
	}
    }

    return textBuffer;
}

/*+/subr**********************************************************************
* NAME	tsTextToStamp - convert text time and date into a time stamp
*
* DESCRIPTION
*	A text string is scanned to produce an EPICS standard time stamp.
*	The text can contain either a time and date (in the local time zone)
*	or a `delta' time from the present local time and date.
*
*	Several different forms of dates and times are accepted by this
*	routine, as outlined below.  In general, this routine accepts
*	strings of the form:
*
*		date time
*
*	If the date is omitted, then today's date is assumed.  (See the
*	exception for times beginning with `-'.)
*
*   Date representations:
*
*	Dates may appear in several variations.  In the forms where the
*	month is represented by letters, case is ignored; only the first
*	3 letters are checked if the month name is spelled out.  In the
*	forms where year is shown as yy, either 2 or 4 digit year can be
*	specified.
*
*	    mm/dd/yy		(yy less than epoch is taken as 20yy)
*	    mon dd, yyyy	(3 or more letters of month)
*	    dd-mon-yy		(3 or more letters of month; yy<epoch is 20yy)
*
*   Time representations:
*
*	Times may appear in several variations on a single form.  One type of
*	variation controls the units for fractional seconds.  Other
*	variations include:
*
*	    a '-' in front of a time indicates a delta time, to be subtracted
*		from the present time and date.  A date isn't allowed to
*		appear with this form of time.
*	    a 'd' before or after a time indicates that the time is to be taken
*		as `daylight savings time'.  This is generally not needed,
*		but is useful for times during the switchover FROM daylight
*		savings time; during the switchover, there is a period when
*		clock times repeat.  (The 'd' can also appear following
*		the "hh", either before the ':' or in place of the ':'.)
*	    an 's' can be similarly used to indicate that the time is to
*		be taken as standard time.  If neither 'd' nor 's' is used
*		for times during the switchover, then 's' is assumed.
*
*	    hh:mm:ss.nano-secs	(9 trailing digits give nano-seconds)
*	    hh:mm:ss.micros	(6 trailing digits give micro-seconds)
*	    hh:mm:ss.mse	(3 trailing digits give milli-seconds)
*	    hh:mm:ss
*
* RETURNS
*	S_ts_OK, or
*	S_ts_inputTextError	if the text doesn't match one of the forms
*				outlined above
*
* BUGS
* o	-time isn't implemented yet
* o	only a single daylight savings time `rule' can be specified
*
* EXAMPLES
* 1.	Get the time stamp equivalent of a text time
*	char		text[]="7/4/91 8:15:30";
*	TS_STAMP	equiv;
*
*	tsTextToStamp(&equiv, &text);
*
*-*/
long
tsTextToStamp(pStamp, pText)
TS_STAMP *pStamp;	/* O time stamp corresponding to text */
char	**pText;	/* IO ptr to ptr to string containing time and date */
{
    long	retStat=S_ts_OK;/* status return to caller */
    long	stat;		/* status from calls */
    struct tsDetail t;		/* detailed breakdown of text */
    TS_STAMP	stamp;		/* temp for building time stamp */
    char	*pField;	/* pointer to field */
    char	delim;		/* delimiter character */
    long	nsec;		/* temp for nano-seconds */
    int		count;		/* count from scan of next field */

    if (needToInitMinWest)
	tsInitMinWest();

    assert(pStamp != NULL);
    assert(pText != NULL);
    assert(*pText != NULL);

/*----------------------------------------------------------------------------
*    skip over leading white space
*----------------------------------------------------------------------------*/
    while (isspace(**pText))
	(*pText)++;

/*----------------------------------------------------------------------------
* date -- first, figure out which format of date is present.  Process each
*	format mostly on its own--final validation of day of month and
*	computing dayYear is done in common for all formats.
*----------------------------------------------------------------------------*/
    if (**pText == '-') {			/* no date--delta time */
	
    }
    else if (strchr(*pText, '/') != 0) {	/* mm/dd/yy[yy] */
	count = nextIntFieldAsInt(pText, &t.monthNum, &delim);
	if (count <= 1 || delim != '/' || t.monthNum <=0 || t.monthNum > 12)
	    retStat = S_ts_inputTextError;
	else
	    t.monthNum--;
	if (retStat == S_ts_OK) {
	    count = nextIntFieldAsInt(pText, &t.dayMonth, &delim);
	    if (count <= 1 || delim != '/' || t.dayMonth <= 0)
		retStat = S_ts_inputTextError;
	}
	if (retStat == S_ts_OK) {
	    count = nextIntFieldAsInt(pText, &t.year, &delim);
	    if (count <= 1 || t.year <= 0)
		retStat = S_ts_inputTextError;
	    else if (!isspace(delim))
		retStat = S_ts_inputTextError;
	}
    }
    else if (strchr(*pText, ',') != 0) {	/* mon[th] dd, yyyy */
	count = nextAlph1UCField(pText, &pField, &delim);
	if (count < 4 || delim != ' ')
	    retStat = S_ts_inputTextError;
	else {
	    for (t.monthNum=0; t.monthNum<12; t.monthNum++) {
		if (strncmp(pField, monthText+3*t.monthNum, 3) == 0)
		    break;
	    }
	    if (t.monthNum > 11)
		retStat = S_ts_inputTextError;
	}
	if (retStat == S_ts_OK) {
	    count = nextIntFieldAsInt(pText, &t.dayMonth, &delim);
	    if (count <= 1 || delim != ',' || t.dayMonth <= 0)
		retStat = S_ts_inputTextError;
	}
	if (retStat == S_ts_OK) {
	    count = nextIntFieldAsInt(pText, &t.year, &delim);
	    if (count < 5 || t.year < TS_EPOCH_YEAR)
		retStat = S_ts_inputTextError;
	    else if (!isspace(delim))
		retStat = S_ts_inputTextError;
	}
    }
    else if (strchr(*pText, '-') != 0) {	/* dd-mon-yy[yy] */
	count = nextIntFieldAsInt(pText, &t.dayMonth, &delim);
	if (count <= 1 || delim != '-' || t.dayMonth <= 0)
	    retStat = S_ts_inputTextError;
	if (retStat == S_ts_OK) {
	    count = nextAlph1UCField(pText, &pField, &delim);
	    if (count < 4 || delim != '-')
		retStat = S_ts_inputTextError;
	    else {
		for (t.monthNum=0; t.monthNum<12; t.monthNum++) {
		    if (strncmp(pField, monthText+3*t.monthNum, 3) == 0)
			break;
		}
		if (t.monthNum > 11)
		    retStat = S_ts_inputTextError;
	    }
	}
	if (retStat == S_ts_OK) {
	    count = nextIntFieldAsInt(pText, &t.year, &delim);
	    if (count <= 1 || t.year <= 0)
		retStat = S_ts_inputTextError;
	    else if (!isspace(delim))
		retStat = S_ts_inputTextError;
	}
    }
    else {					/* assume date omitted */
	if ((stat = tsLocalTime(&stamp)) != S_ts_OK)
	    retStat = stat;
	else {
	    tsStampToLocal(stamp, &t);
	    t.dayMonth++;
	}
    }

/*----------------------------------------------------------------------------
*	finish processing on date.  t.year has 2 or 4 digit year, t.monthNum
*	has month (0-11), and t.dayMonth has day of month, beginning with 1.
*----------------------------------------------------------------------------*/
    if (retStat == S_ts_OK) {
	if (t.year < 100) {
	    t.year += 1900;
	    if (t.year < TS_EPOCH_YEAR)
		t.year += 100;
	}
	else if (t.year < TS_EPOCH_YEAR)
	    retStat = S_ts_inputTextError;
    }
    if (retStat == S_ts_OK) {
	if (t.year > TS_MAX_YEAR)
	    retStat = S_ts_inputTextError;
    }
    if (retStat == S_ts_OK) {
	if (t.year % 400 == 0 || (t.year % 4 == 0 && t.year % 100 != 0))
	    t.leapYear = 1;
	else
	    t.leapYear = 0;
	if (t.dayMonth <= daysInMonth[t.monthNum] ||
	     (t.monthNum == 1 && t.dayMonth <= daysInMonth[1] + t.leapYear)) {
	    t.dayMonth--;
	    t.dayYear = dayYear1stOfMon[t.monthNum] + t.dayMonth;
	    if (t.monthNum > 1)
		t.dayYear += t.leapYear;
	}
	else
	    retStat = S_ts_inputTextError;
    }

/*----------------------------------------------------------------------------
* time -- first, suppress leading white space and then check for 'd' or 's'
*	as the initial character.
*----------------------------------------------------------------------------*/
    nsec = 0;
    if (retStat == S_ts_OK) {
	while (isspace(**pText))
	    (*pText)++;
	t.dstOverlapChar = ':';
	if (**pText == 'd' || **pText == 's') {
	    t.dstOverlapChar = **pText;
	    (*pText)++;
	}
	count = nextIntFieldAsInt(pText, &t.hours, &delim);
	if (count <= 1 || t.hours < 0 || t.hours > 23)
	    retStat = S_ts_inputTextError;
	else if (delim == 'd' || delim == 's') {
	    t.dstOverlapChar = delim;
	    if (**pText == ':')
		(*pText)++;		/* skip the 'extra' delimiter */
	}
	else if (delim != ':')
	    retStat = S_ts_inputTextError;
        if (retStat == S_ts_OK) {
	    count = nextIntFieldAsInt(pText, &t.minutes, &delim);
	    if (count <= 1 || t.minutes < 0 || t.minutes > 59)
		retStat = S_ts_inputTextError;
	    else if (delim != ':')
		retStat = S_ts_inputTextError;
	}
        if (retStat == S_ts_OK) {
	    count = nextIntFieldAsInt(pText, &t.seconds, &delim);
	    if (count <= 1 || t.seconds < 0 || t.seconds > 59)
		retStat = S_ts_inputTextError;
	}
        if (retStat == S_ts_OK && delim == '.') {
	    count = nextIntFieldAsLong(pText, &nsec, &delim);
	    if (count <= 1 || nsec < 0)
		retStat = S_ts_inputTextError;
	    else if (count == 4) {
		if (nsec > 999)
		    retStat = S_ts_inputTextError;
		else
		    nsec *= 1000000;
	    }
	    else if (count == 7) {
		if (nsec > 999999)
		    retStat = S_ts_inputTextError;
		else
		    nsec *= 1000;
	    }
	    else if (count == 10) {
		if (nsec > 999999999)
		    retStat = S_ts_inputTextError;
	    }
	    else
		retStat = S_ts_inputTextError;
	}
	if (retStat == S_ts_OK) {
	    if (delim == 'd' || delim == 's')
		t.dstOverlapChar = delim;
	}
    }

    if (retStat == S_ts_OK) {
	tsStampFromLocal(&stamp, &t);
	stamp.nsec = nsec;
    }
    else {
	stamp.secPastEpoch = 0;
	stamp.nsec = nsec;
    }

    *pStamp = stamp;
    return retStat;
}

/*+/subr**********************************************************************
* NAME	tsTimeTextToStamp - convert text time into a time stamp
*
* DESCRIPTION
*	A text string is scanned to produce an EPICS standard time stamp.
*	The text must contain a time representation as described for
*	tsTextToStamp().  (If either 'd' or 's' occurs in the text time,
*	it is ignored.)  (Since EPICS time stamps are unsigned, the text
*	may not be signed.)
*
* RETURNS
*	S_ts_OK, or
*	S_ts_inputTextError	if the text isn't in a legal form
*
* EXAMPLES
* 1.	Get the time stamp equivalent for an elapsed time.
*	char		text[]="15:31:22.888";
*	TS_STAMP	equiv;
*
*	tsTimeTextToStamp(&equiv, &text);
*
*-*/
long
tsTimeTextToStamp(pStamp, pText)
TS_STAMP *pStamp;	/* O time stamp corresponding to text */
char	**pText;	/* IO ptr to ptr to string containing time and date */
{
    long	retStat=S_ts_OK;/* status return to caller */
    long	stat;		/* status from calls */
    struct tsDetail t;		/* detailed breakdown of text */
    TS_STAMP	stamp;		/* temp for building time stamp */
    char	*pField;	/* pointer to field */
    char	delim;		/* delimiter character */
    int		count;		/* count from scan of next field */
    long	nsec;		/* temp for nano-seconds */

    assert(pStamp != NULL);
    assert(pText != NULL);
    assert(*pText != NULL);

/*----------------------------------------------------------------------------
*    skip over leading white space
*----------------------------------------------------------------------------*/
    while (isspace(**pText))
	(*pText)++;

/*----------------------------------------------------------------------------
* time -- first, suppress leading white space and then check for 'd' or 's'
*	as the initial character.
*----------------------------------------------------------------------------*/
    stamp.secPastEpoch = 0;
    nsec = 0;
    if (retStat == S_ts_OK) {
	while (isspace(**pText))
	    (*pText)++;
	t.dstOverlapChar = ':';
	if (**pText == 'd' || **pText == 's') {
	    t.dstOverlapChar = **pText;
	    (*pText)++;
	}
	count = nextIntFieldAsInt(pText, &t.hours, &delim);
	if (count <= 1 || t.hours < 0 || t.hours > 23)
	    retStat = S_ts_inputTextError;
	else if (delim == 'd' || delim == 's') {
	    t.dstOverlapChar = delim;
	    if (**pText == ':')
		(*pText)++;		/* skip the 'extra' delimiter */
	}
	else if (delim != ':')
	    retStat = S_ts_inputTextError;
        if (retStat == S_ts_OK) {
	    count = nextIntFieldAsInt(pText, &t.minutes, &delim);
	    if (count <= 1 || t.minutes < 0 || t.minutes > 59)
		retStat = S_ts_inputTextError;
	    else if (delim != ':')
		retStat = S_ts_inputTextError;
	}
        if (retStat == S_ts_OK) {
	    count = nextIntFieldAsInt(pText, &t.seconds, &delim);
	    if (count <= 1 || t.seconds < 0 || t.seconds > 59)
		retStat = S_ts_inputTextError;
	}
        if (retStat == S_ts_OK && delim == '.') {
	    count = nextIntFieldAsLong(pText, &nsec, &delim);
	    if (count <= 1 || nsec < 0)
		retStat = S_ts_inputTextError;
	    else if (count == 4) {
		if (nsec > 999)
		    retStat = S_ts_inputTextError;
		else
		    nsec *= 1000000;
	    }
	    else if (count == 7) {
		if (nsec > 999999)
		    retStat = S_ts_inputTextError;
		else
		    nsec *= 1000;
	    }
	    else if (count == 10) {
		if (nsec > 999999999)
		    retStat = S_ts_inputTextError;
	    }
	    else
		retStat = S_ts_inputTextError;
	}
    }

/*----------------------------------------------------------------------------
* convert tsDetail structure into a time stamp, after doing a little more
*	refinement of the tsDetail structure
*----------------------------------------------------------------------------*/
    if (retStat == S_ts_OK) {
	stamp.secPastEpoch += t.hours*3600 + t.minutes*60 + t.seconds;
	stamp.nsec = nsec;
    }

    *pStamp = stamp;
    return retStat;
}
