diff --git a/src/libCom/nextFieldSubr.c b/src/libCom/nextFieldSubr.c new file mode 100644 index 000000000..1fbf674de --- /dev/null +++ b/src/libCom/nextFieldSubr.c @@ -0,0 +1,315 @@ +/* $Id$ + * Author: Roger A. Cole + * Date: 10-10-90 + * + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1991, 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 10-10-90 rac initial version + * .01 06-18-91 rac installed in SCCS + * + * 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 nextFieldSubr.c - text field scanning routines +* +* GENERAL DESCRIPTION +* The routines in this module provide for scanning fields in text +* strings. They can be used as the basis for parsing text input +* to a program. +* +* QUICK REFERENCE +* char (*pText)[]; +* char (*pField)[]; +* char *pDelim; +* +* int nextAlphField( <>pText, >pField, >pDelim ) +* int nextAlph1UCField( <>pText, >pField, >pDelim ) +* int nextANField( <>pText, >pField, >pDelim ) +* int nextChanNameField( <>pText, >pField, >pDelim ) +* int nextFltField( <>pText, >pField, >pDelim ) +* int nextFltFieldAsDbl( <>pText, >pDblVal, >pDelim ) +* int nextIntField( <>pText, >pField, >pDelim ) +* int nextIntFieldAsInt( <>pText, >pIntVal, >pDelim ) +* int nextIntFieldAsLong( <>pText, >pLongVal, >pDelim ) +* int nextNonSpace( <>pText ) +* int nextNonSpaceField( <>pText, >pField, >pDelim ) +* +* DESCRIPTION +* The input text string is scanned to identify the beginning and +* end of a field. At return, the input pointer points to the character +* following the delimiter and the delimiter has been returned through +* its pointer; the field contents are `returned' either as a pointer +* to the first character of the field or as a value returned through +* a pointer. +* +* In the input string, a '\0' is stored in place of the delimiter, +* so that standard string handling tools can be used for text fields. +* +* nextAlphField scans the next alphabetic field +* nextAlph1UCField scans the next alphabetic field, changes +* the first character to upper case, and +* changes the rest to lower case +* nextANField scans the next alpha-numeric field +* nextChanNameField scans the next field as a channel name, +* delimited by white space or a comma +* nextFltField scans the next float field +* nextFltFieldAsDbl scans the next float field as a double +* nextIntField scans the next integer field +* nextIntFieldAsInt scans the next integer field as an int +* nextIntFieldAsLong scans the next integer field as a long +* nextNonSpace scans to the next non-space character +* nextNonSpaceField scans the next field, delimited by white +* space +* +* RETURNS +* count of characters in field, including the delimiter. A special +* case exists when only '\0' is encountered; in this case 0 is returned. +* +* BUGS +* o use of type checking macros isn't protected by isascii() +* +* SEE ALSO +* tsTextToStamp() +* +* NOTES +* 1. fields involving alpha types consider underscore ('_') to be +* alphabetic. +* +* EXAMPLE +* char text[]="process 30 samples" +* char *pText; pointer into text string +* char *pCmd; pointer to first field, to use as a command +* int count; value of second field, number of items to process +* char *pUnits; pointer to third field, needed for command processing +* int length; length of field +* char delim; delimiter for field +* +* pText = text; +* if (nextAlphField(&pText, &pCmd, &delim) <= 1) +* error action if empty field +* if (nextIntFieldAsInt(&pText, &count, &delim) <= 1) +* error action if empty field +* if (nextAlphField(&pText, &pUnits, &delim) <= 1) +* error action if empty field +* printf("command=%s, count=%d, units=%s\n", pCmd, count, pUnits); +* +*-***************************************************************************/ +#include +#ifdef vxWorks +# include +#else +# include +#endif + +#define NEXT_PREAMBLE \ + char *pDlm; /* pointer to field delimiter */ \ + int count; /* count of characters (plus delim) */ \ + \ + assert(ppText != NULL); \ + assert(*ppText != NULL); \ + assert(ppField != NULL); \ + assert(pDelim != NULL); \ + \ + if (**ppText == '\0') { \ + *ppField = *ppText; \ + *pDelim = **ppText; \ + return 0; \ + } \ + while (**ppText != '\0' && isspace(**ppText)) \ + (*ppText)++; /* skip leading white space */ \ + pDlm = *ppField = *ppText; \ + if (*pDlm == '\0') { \ + *pDelim = **ppText; \ + return 0; \ + } \ + count = 1; /* include delimiter in count */ +#define NEXT_POSTAMBLE \ + pDlm++; \ + count++; \ + } \ + *pDelim = *pDlm; \ + *ppText = pDlm; \ + if (*pDlm != '\0') { \ + (*ppText)++; /* point to next available character */ \ + *pDlm = '\0'; \ + } + +int +nextAlphField(ppText, ppField, pDelim) +char **ppText; /* I/O pointer to pointer to text to scan */ +char **ppField; /* O pointer to pointer to field */ +char *pDelim; /* O pointer to return field's delimiter */ +{ + NEXT_PREAMBLE + while (isalpha(*pDlm) || *pDlm == '_') { + NEXT_POSTAMBLE + return count; +} +int +nextAlph1UCField(ppText, ppField, pDelim) +char **ppText; /* I/O pointer to pointer to text to scan */ +char **ppField; /* O pointer to pointer to field */ +char *pDelim; /* O pointer to return field's delimiter */ +{ + NEXT_PREAMBLE + while (isalpha(*pDlm) || *pDlm == '_') { + if (count == 1) { + if (islower(*pDlm)) + *pDlm = toupper(*pDlm); + } + else { + if (isupper(*pDlm)) + *pDlm = tolower(*pDlm); + } + NEXT_POSTAMBLE + return count; +} +int +nextANField(ppText, ppField, pDelim) +char **ppText; /* I/O pointer to pointer to text to scan */ +char **ppField; /* O pointer to pointer to field */ +char *pDelim; /* O pointer to return field's delimiter */ +{ + NEXT_PREAMBLE + while (isalnum(*pDlm) || *pDlm == '_') { + NEXT_POSTAMBLE + return count; +} +int +nextChanNameField(ppText, ppField, pDelim) +char **ppText; /* I/O pointer to pointer to text to scan */ +char **ppField; /* O pointer to pointer to field */ +char *pDelim; /* O pointer to return field's delimiter */ +{ + NEXT_PREAMBLE + while (!isspace(*pDlm)) { + if (*pDlm == '\0') + break; + else if (*pDlm == ',') + break; + NEXT_POSTAMBLE + return count; +} +int +nextFltField(ppText, ppField, pDelim) +char **ppText; /* I/O pointer to pointer to text to scan */ +char **ppField; /* O pointer to pointer to field */ +char *pDelim; /* O pointer to return field's delimiter */ +{ + NEXT_PREAMBLE + while (isdigit(*pDlm) || *pDlm=='-' || *pDlm=='+' || *pDlm=='.') { + NEXT_POSTAMBLE + return count; +} +int +nextFltFieldAsDbl(ppText, pDblVal, pDelim) +char **ppText; /* I/O pointer to pointer to text to scan */ +double *pDblVal; /* O pointer to return field's value */ +char *pDelim; /* O pointer to return field's delimiter */ +{ + char *pField; /* pointer to field */ + int count; /* count of char in field, including delim */ + + assert(pDblVal != NULL); + + count = nextFltField(ppText, &pField, pDelim); + if (count > 1) { + if (sscanf(pField, "%lf", pDblVal) != 1) + assert(0); + } + + return count; +} +int +nextIntField(ppText, ppField, pDelim) +char **ppText; /* I/O pointer to pointer to text to scan */ +char **ppField; /* O pointer to pointer to field */ +char *pDelim; /* O pointer to return field's delimiter */ +{ + NEXT_PREAMBLE + while (isdigit(*pDlm) || ((*pDlm=='-' || *pDlm=='+') && count==1)) { + NEXT_POSTAMBLE + return count; +} +int +nextIntFieldAsInt(ppText, pIntVal, pDelim) +char **ppText; /* I/O pointer to pointer to text to scan */ +int *pIntVal; /* O pointer to return field's value */ +char *pDelim; /* O pointer to return field's delimiter */ +{ + char *pField; /* pointer to field */ + int count; /* count of char in field, including delim */ + + assert(pIntVal != NULL); + + count = nextIntField(ppText, &pField, pDelim); + if (count > 1) { + if (sscanf(pField, "%d", pIntVal) != 1) + assert(0); + } + + return count; +} +int +nextIntFieldAsLong(ppText, pLongVal, pDelim) +char **ppText; /* I/O pointer to pointer to text to scan */ +long *pLongVal; /* O pointer to return field's value */ +char *pDelim; /* O pointer to return field's delimiter */ +{ + char *pField; /* pointer to field */ + int count; /* count of char in field, including delim */ + + assert(pLongVal != NULL); + + count = nextIntField(ppText, &pField, pDelim); + if (count > 1) { + if (sscanf(pField, "%ld", pLongVal) != 1) + assert(0); + } + + return count; +} +int +nextNonSpace(ppText) +char **ppText; /* I/O pointer to pointer to text to scan */ +{ + while (isspace(**ppText)) + (*ppText)++; + return 0; +} +int +nextNonSpaceField(ppText, ppField, pDelim) +char **ppText; /* I/O pointer to pointer to text to scan */ +char **ppField; /* O pointer to pointer to field */ +char *pDelim; /* O pointer to return field's delimiter */ +{ + NEXT_PREAMBLE + while (!isspace(*pDlm)) { + NEXT_POSTAMBLE + return count; +} diff --git a/src/libCom/sydSubrPFO.c b/src/libCom/sydSubrPFO.c new file mode 100644 index 000000000..19426c641 --- /dev/null +++ b/src/libCom/sydSubrPFO.c @@ -0,0 +1,220 @@ +/* $Id$ + * Author: Roger A. Cole + * Date: 12-04-90 + * + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1991, 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 12-04-90 rac initial version + * .01 06-18-91 rac installed in SCCS + * + * 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 sydSubrPFO.c - acquire synchronous samples from `gsd_...' routines +* +* DESCRIPTION +* +*-***************************************************************************/ +#include +#define SYD_PRIVATE +#include +#include +#ifndef INC_tsDefs_h +# include +#endif +#ifndef INCLcadefh +# include +#endif +#include +#define PFO_CHAN_DEF +#include + +#ifdef vxWorks +# include +# include /* for O_RDWR and O_RDONLY definitions */ +# include +#else +# include +# include /* for O_RDWR and O_RDONLY definitions */ +#endif + +long sydPFOFunc(); +long sydPFOFuncGetGR(); + +long +sydOpenPFO(ppSspec, pHandle) +SYD_SPEC **ppSspec; /* O pointer to synchronous set spec pointer */ +void *pHandle; /* I pointer to handle for "source" */ +{ + long stat; + + assert(ppSspec != NULL); + + if ((*ppSspec = (SYD_SPEC *)GenMalloc(sizeof(SYD_SPEC))) == NULL) + return S_syd_noMem; + (*ppSspec)->pFunc = sydPFOFunc; + (*ppSspec)->type = SYD_TY_PFO; + if ((stat = sydPFOFunc(*ppSspec, NULL, SYD_FC_INIT, pHandle)) != S_syd_OK){ + GenFree((char *)*ppSspec); + *ppSspec = NULL; + return stat; + } + (*ppSspec)->nInBufs = 2; + return sydOpen(ppSspec, pHandle); +} +/*+/subr********************************************************************** +* NAME sydPFOFunc - handle gsd_xxx sync data interactions +* +* DESCRIPTION +* +* sydPFOFunc(pSspec, NULL, SYD_FC_INIT, NULL) +* sydPFOFunc(pSspec, pSChan, SYD_FC_OPEN, pPfoChan) +* sydPFOFunc(pSspec, pSChan, SYD_FC_READ, NULL) +* sydPFOFunc(pSspec, pSChan, SYD_FC_POSITION, NULL) +* sydPFOFunc(pSspec, pSChan, SYD_FC_CLOSE, NULL) +* sydPFOFunc(pSspec, NULL, SYD_FC_FILEINFO, NULL) +* sydPFOFunc(pSspec, NULL, SYD_FC_WRAPUP, NULL) +* +* RETURNS +* S_syd_OK, or +* other code indicating error +* +* BUGS +* o text +* +* SEE ALSO +* +* EXAMPLE +* +*-*/ +long +sydPFOFunc(pSspec, pStruct, funcCode, pArg) +SYD_SPEC *pSspec; /* IO pointer to synchronous set spec */ +void *pStruct; /* IO pointer to data struct used by funcCode */ +enum sydFuncCode funcCode;/* I function code */ +void *pArg; /* I pointer to arg, as required by funcCode */ +{ + SYD_CHAN *pSChan; /* pointer to syncSet channel descriptor */ + PFO_CHAN *pPChan; /* pointer to pfo channel struct */ + struct gsd_sync_data *pSyn; /* pointer to pfo sync data struct */ + long retStat=S_syd_OK; + int bufNum, nBytes; + + pSChan = (SYD_CHAN *)pStruct; + + if (funcCode == SYD_FC_INIT) { + pSspec->pHandle = NULL; + } + else if (funcCode == SYD_FC_OPEN) { + pPChan = (PFO_CHAN *)pArg; + pSChan->pHandle = (void *)pPChan->pGsd; + pSChan->dbrType = pPChan->pGsd->time_type; + assert(dbr_type_is_TIME(pSChan->dbrType)); + pSChan->elCount = pPChan->pGsd->count; + sydPFOFuncGetGR(pSspec, pSChan, pPChan); + } + else if (funcCode == SYD_FC_READ) { + pSyn = (struct gsd_sync_data *)pSChan->pHandle; + if (pSyn->svalid != 0) { + bufNum = NEXT_INBUF(pSChan, pSChan->lastInBuf); + nBytes = dbr_size_n(pSChan->dbrType, pSChan->elCount); + bcopy((char *)pSyn->pSdata, (char *)pSChan->pInBuf[bufNum], nBytes); + pSChan->inStatus[bufNum] = SYD_B_FULL; + pSChan->lastInBuf = bufNum; + if (pSChan->firstInBuf < 0) + pSChan->firstInBuf = bufNum; + } + } + else if (funcCode == SYD_FC_POSITION) { + ; /* no action */ + } + else if (funcCode == SYD_FC_CLOSE) { + ; /* no action */ + } + else if (funcCode == SYD_FC_FILEINFO) { + ; /* no action */ + } + else if (funcCode == SYD_FC_WRAPUP) { + pSspec->pHandle = NULL; + } + + return retStat; +} + +static long +sydPFOFuncGetGR(pSspec, pSChan, pPChan) +SYD_SPEC *pSspec; /* pointer to syncSet specification */ +SYD_CHAN *pSChan; /* pointer to syncSet channel descriptor */ +PFO_CHAN *pPChan; /* pointer to pfo channel struct */ +{ + int i; + + if (pSChan->dbrType == DBR_TIME_FLOAT) { + assert(pPChan->dbrGrType == DBR_GR_FLOAT || + pPChan->dbrGrType == DBR_CTRL_FLOAT); + pSChan->grBuf.gfltval = pPChan->pDbrGr->gfltval; + pSChan->dbrGrType = DBR_GR_FLOAT; + } + else if (pSChan->dbrType == DBR_TIME_SHORT) { + assert(pPChan->dbrGrType == DBR_GR_SHORT || + pPChan->dbrGrType == DBR_CTRL_SHORT); + pSChan->grBuf.gshrtval = pPChan->pDbrGr->gshrtval; + pSChan->dbrGrType = DBR_GR_SHORT; + } + else if (pSChan->dbrType == DBR_TIME_DOUBLE) { + assert(pPChan->dbrGrType == DBR_GR_DOUBLE || + pPChan->dbrGrType == DBR_CTRL_DOUBLE); + pSChan->grBuf.gdblval = pPChan->pDbrGr->gdblval; + pSChan->dbrGrType = DBR_GR_DOUBLE; + } + else if (pSChan->dbrType == DBR_TIME_LONG) { + assert(pPChan->dbrGrType == DBR_GR_LONG || + pPChan->dbrGrType == DBR_CTRL_LONG); + pSChan->grBuf.glngval = pPChan->pDbrGr->glngval; + pSChan->dbrGrType = DBR_GR_LONG; + } + else if (pSChan->dbrType == DBR_TIME_STRING) { + assert(pPChan->dbrGrType == DBR_GR_STRING || + pPChan->dbrGrType == DBR_CTRL_STRING); + pSChan->grBuf.gstrval = pPChan->pDbrGr->gstrval; + pSChan->dbrGrType = DBR_GR_STRING; + } + else if (pSChan->dbrType == DBR_TIME_ENUM) { + assert(pPChan->dbrGrType == DBR_GR_ENUM || + pPChan->dbrGrType == DBR_CTRL_ENUM); + pSChan->grBuf.genmval = pPChan->pDbrGr->genmval; + pSChan->dbrGrType = DBR_GR_ENUM; + } + else if (pSChan->dbrType == DBR_TIME_CHAR) { + assert(pPChan->dbrGrType == DBR_GR_CHAR || + pPChan->dbrGrType == DBR_CTRL_CHAR); + pSChan->grBuf.gchrval = pPChan->pDbrGr->gchrval; + pSChan->dbrGrType = DBR_GR_FLOAT; + } + return S_syd_OK; +} diff --git a/src/libCom/sydSubrSSF.c b/src/libCom/sydSubrSSF.c new file mode 100644 index 000000000..c811f230f --- /dev/null +++ b/src/libCom/sydSubrSSF.c @@ -0,0 +1,942 @@ +/* $Id$ + * Author: Roger A. Cole + * Date: 12-04-90 + * + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1991, 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 12-04-90 rac initial version + * .01 06-18-91 rac installed in SCCS + * + * 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 sydSubr.c - acquire synchronous samples from AR `sample set' files +* +* DESCRIPTION +* +*-***************************************************************************/ +#include +#define SYD_PRIVATE +#include +#include +#ifndef INC_tsDefs_h +# include +#endif +#ifndef INCLcadefh +# include +#endif + +#ifdef vxWorks +# include +# include /* for O_RDWR and O_RDONLY definitions */ +# include +# include +#else +# include +# include /* for O_RDWR and O_RDONLY definitions */ +# include +# include +#endif + +long sydSSFFunc(); +long sydSSFFuncGetGR(); +long sydSSFFuncReadData(); +long sydSSFFuncSeekSample(); +long sydSSFFuncSeekStamp(); + +/*+/macro********************************************************************* +* NAME posXxx - text file search and positioning routines +* +* DESCRIPTION +* These routines allow setting the position for text files. The +* file may be positioned at its beginning or its end. It is +* supported to search, either forward or backward, for a string, +* leaving the file positioned at the beginning of the string. +* +* long pos; +* FILE *pStream; +* +* pos = posBOF(pStream); +* pos = posEOF(pStream); +* pos = posKeyFwd("key", pStream); +* pos = posKeyRev("key", pStream, pos); +* pos = posPos(pStream, pos); +* +* RETURNS +* position, or +* <0 if an error occurs +* +* BUGS +* o the use of fseek and ftell doesn't match the Standard C specification +* and may be non-portable. They have been verified to function +* properly under SunOS 4.1 and VxWorks 4.0.2. +* +*-*/ +static long posBOF(); +static long posEOF(); +static long posKeyFwd(); +static long posKeyRev(); +static long posPos(); + +static long +posBOF(pStream) +FILE *pStream; +{ + if (fseek(pStream, 0L, 0) < 0) { + perror("posBOF: fseek error"); + return -1; + } + return ftell(pStream); +} + +static long +posEOF(pStream) +FILE *pStream; +{ + if (fseek(pStream, 0L, 2) < 0) { + perror("posEOF: fseek error"); + return -1; + } + return ftell(pStream); +} + +static long +posKeyFwd(key, pStream) +char *key; +FILE *pStream; +{ + int i, lastChar; + int c; + long pos; + + lastChar = strlen(key) - 1; + i = 0; + + if ((pos = ftell(pStream)) < 0) { + if (!feof(pStream)) + perror("posKeyFwd: ftell error"); + return -1; + } + + while (1) { + if ((c = fgetc(pStream)) == EOF) + return -1; + if ((char)c == key[i]) { + if (i == lastChar) + break; + i++; + } + else { + if (i == 0) { + if ((pos = ftell(pStream)) < 0) { + if (!feof(pStream)) + perror("posKeyFwd: ftell error"); + return -1; + } + } + else if (i == 1) { + pos++; + if ((char)c != key[0]) + i = 0; + } + else { + pos++; + if (fseek(pStream, pos, 0) != 0) { + if (!feof(pStream)) + perror("posKeyFwd: fseek error"); + return -1; + } + i = 0; + } + } + } + if (fseek(pStream, pos, 0) != 0) { + if (!feof(pStream)) + perror("posKeyFwd: fseek error"); + return -1; + } + return pos; +} + +static long +posKeyRev(key, pStream, lastPos) +char *key; +FILE *pStream; +long lastPos; /* I position to start search if reverse; -1 + says to start at EOF */ +{ + int i, lastChar; + int c; + long pos; + + lastChar = i = strlen(key) - 1; + + if (lastPos >= 0) { + if (fseek(pStream, lastPos, 0) != 0) { + if (!feof(pStream)) + perror("posKeyRev: fseek error"); + return -1; + } + } + else { + if (fseek(pStream, 0L, 2) != 0) { + if (!feof(pStream)) + perror("posKeyRev: fseek error"); + return -1; + } + } + while (1) { + if (ftell(pStream) <= 0) + return -1; + if (fseek(pStream, -1L, 1) != 0) { + if (!feof(pStream)) + perror("posKeyRev: fseek error"); + return -1; + } + if ((c = fgetc(pStream)) == EOF) + return -1; + if (fseek(pStream, -1L, 1) != 0) { + if (!feof(pStream)) + perror("posKeyRev: fseek error"); + return -1; + } + if ((char)c == key[i]) { + if (i == 0) + break; + i--; + } + else + i = lastChar; + } + if ((pos = ftell(pStream)) < 0) + perror("posKeyRev: ftell error"); + return pos; +} + +static long +posPos(pStream, pos) +FILE *pStream; +long pos; +{ + if (fseek(pStream, pos, 0) < 0) { + perror("posPos: fseek error"); + return -1; + } + return ftell(pStream); +} + +long +sydOpenSSF(ppSspec, pHandle) +SYD_SPEC **ppSspec; /* O pointer to synchronous set spec pointer */ +void *pHandle; /* I pointer to handle for "source" */ +{ + long stat; + + assert(ppSspec != NULL); + + if ((*ppSspec = (SYD_SPEC *)GenMalloc(sizeof(SYD_SPEC))) == NULL) + return S_syd_noMem; + (*ppSspec)->pFunc = sydSSFFunc; + (*ppSspec)->type = SYD_TY_SSF; + if ((stat = sydSSFFunc(*ppSspec, NULL, SYD_FC_INIT, pHandle)) != S_syd_OK){ + GenFree((char *)*ppSspec); + *ppSspec = NULL; + return stat; + } + (*ppSspec)->nInBufs = 2; + return sydOpen(ppSspec, pHandle); +} +/*+/subr********************************************************************** +* NAME sydSSFFunc - handle "sample set" data file interactions +* +* DESCRIPTION +* +* sydSSFFunc(pSspec, NULL, SYD_FC_INIT, fileName) open "sample set" file +* sydSSFFunc(pSspec, pSChan, SYD_FC_OPEN, NULL) chanName already in pSChan +* sydSSFFunc(pSspec, pSChan, SYD_FC_READ, NULL) +* sydSSFFunc(pSspec, pSChan, SYD_FC_POSITION, &stamp) +* sydSSFFunc(pSspec, pSChan, SYD_FC_CLOSE, NULL) +* sydSSFFunc(pSspec, NULL, SYD_FC_FILEINFO, outStream) +* sydSSFFunc(pSspec, NULL, SYD_FC_WRAPUP, NULL) close "sample set" file +* +* RETURNS +* S_syd_OK, or +* S_syd_EOF, or +* other code indicating error +* +* BUGS +* o doesn't detect or report EOF +* o doesn't do anything (or even detect) overwriting un-sampled +* buffers (for SYD_FC_READ) +* o needs a "get graphics information" function and function code +* +* SEE ALSO +* +* EXAMPLE +* +*-*/ +long +sydSSFFunc(pSspec, pStruct, funcCode, pArg) +SYD_SPEC *pSspec; /* IO pointer to synchronous set spec */ +void *pStruct; /* IO pointer to data struct used by funcCode */ +enum sydFuncCode funcCode;/* I function code */ +void *pArg; /* I pointer to arg, as required by funcCode */ +{ + SYD_CHAN *pSChan; /* pointer to syncSet channel descriptor */ + FILE *ssFile; /* file pointer for sample set data file */ + long retStat=S_syd_OK; + long stat; + int i; + long pos; + char record[120]; + char recDelim; + char *pRecord; + char *pField; + char delim; + char chanName[db_name_dim+2]; + chtype type; + int elCount; + + pSChan = (SYD_CHAN *)pStruct; + if (pSspec != NULL) + ssFile = (FILE *)pSspec->pHandle; + + if (funcCode == SYD_FC_INIT) { + if ((ssFile = fopen((char *)pArg, "r")) == NULL) { + (void)printf("couldn't open %s\n", (char *)pArg); + perror(NULL); + retStat = S_syd_ERROR; + } + pSspec->pHandle = (void *)ssFile; + } + else if (funcCode == SYD_FC_OPEN) { + (void)posBOF(ssFile); + if ((pos = posKeyFwd("%endData%", ssFile)) < 0) { + (void)printf("couldn't find %%endData%%\n"); + (void)posBOF(ssFile); + return S_syd_chanNotFound; + } + strcpy(chanName, "\n"); + strcat(chanName, pSChan->name); + strcat(chanName, " "); + if ((pos = posKeyRev(chanName, ssFile, pos)) < 0) + return S_syd_chanNotFound; + assert(sizeof(record) == 120); + fgetc(ssFile); /* skip over the \n */ + if (fgets(record, 120, ssFile) == NULL) + assertAlways(0); + pRecord = record; + recDelim = record[strlen(record)-1]; + if (nextChanNameField(&pRecord, &pField, &delim) <= 1) + assertAlways(0); + if (strcmp(pField, pSChan->name) != 0) + assertAlways(0); + pSChan->pHandle = NULL; + if (nextAlphField(&pRecord, &pField, &delim) <= 1) + assertAlways(0); + dbf_text_to_type(pField, type); + assert(type >= 0); + pSChan->dbrType = dbf_type_to_DBR_TIME(type); + pSChan->dbrGrType = dbf_type_to_DBR_GR(type); + stat = sscanf(pRecord, "%*d %*d %d", &elCount); + assert(stat == 1); + pSChan->elCount = elCount; + pSChan->sync = SYD_SY_NONF; /* force channel to be sync */ + sydSSFFuncGetGR(pSspec, pSChan); + (void)posBOF(ssFile); + } + else if (funcCode == SYD_FC_READ) { + TS_STAMP timeStamp; + enum sydBStatus bufStat; + int bufNum, oldBufNum; + +/*----------------------------------------------------------------------------- +* find the next line with SAMPLE: and get the time stamp. When done: +* o bufStat will be one of: +* SYD_B_RESTART this sample starts a new "run" of data +* SYD_B_FULL this is an ordinary sample +* SYD_B_EOF end of file +* o the file will be positioned following the %endHeader% line +*----------------------------------------------------------------------------*/ + bufStat = sydSSFFuncSeekSample(ssFile, &timeStamp); + +/*----------------------------------------------------------------------------- +* now, read the actual data. This is done by processing all the data +* lines in the file, storing those which match a channel in the synchronous +* set. (This is somewhat contrary to the "advertising" for this function +* code, which implies that only a single channel is read.) When done: +* o all channels in the set will have an appropriate buffer status +* o all channels will have the same time stamp +* o all channels with data will have the data in the buffer +* o all channels will have .lastInBuf updated +*----------------------------------------------------------------------------*/ + pSChan = pSspec->pChanHead; + oldBufNum = pSChan->lastInBuf; + bufNum = NEXT_INBUF(pSChan, oldBufNum); + while (pSChan != NULL) { + if (bufStat != SYD_B_EOF) + pSChan->inStatus[bufNum] = SYD_B_MISSING; + else + pSChan->inStatus[bufNum] = bufStat; + pSChan->pInBuf[bufNum]->tfltval.stamp = timeStamp; + pSChan->lastInBuf = bufNum; + if (pSChan->firstInBuf < 0) + pSChan->firstInBuf = bufNum; + pSChan = pSChan->pNext; + } + if (bufStat == SYD_B_EOF) + return S_syd_EOF; + while (1) { + stat = fscanf(ssFile, "%s", chanName); + assert(stat == 1); + if ((pSChan = sydChanFind(pSspec, chanName)) != NULL) { + (void)fscanf(ssFile, "%*s"); /* skip DBF_xxx */ + stat = sydSSFFuncReadData(pSChan, ssFile, + bufStat, bufNum, oldBufNum); + } + else if (strcmp(chanName, "%endData%") == 0) + break; + else { + while ((delim = fgetc(ssFile)) != EOF && delim != '\n') + ; /* keep skipping until end of line */ + } + } + } + else if (funcCode == SYD_FC_POSITION) { + TS_STAMP *pStamp; + TS_STAMP stamp, earlyStamp; + long earlyPos; + + pStamp = (TS_STAMP *)pArg; + if (pStamp == NULL) { + (void)posBOF(ssFile); + return retStat; + } +/*----------------------------------------------------------------------------- +* find 2 adjacent samples to make it easy to determine which way to +* search. There are several cases: +* 1. the file is at least 2 away from EOF and both stamps can be found +* forward +* 2. the file is 1 away from EOF--1 stamp backward and 1 forward +* 3. the file contains no stamps +* 4. the file is at EOF and both stamps must be found backward +* 5. the file only contains one stamp +* +* Cases 2 and 4 are treated by rewinding the file to avoid any backward +* searching. This transforms those cases into one of the other cases. +* +* When this section is done, `earlyStamp' and `stamp' will contain two +* adjacent stamps. If they are equal, then case 4 exists. The file +* will be positioned following `stamp'. +*----------------------------------------------------------------------------*/ + earlyPos = sydSSFFuncSeekStamp(ssFile, &earlyStamp, 0, 0L); + pos = sydSSFFuncSeekStamp(ssFile, &stamp, 0, 0L); + if (earlyPos < 0 || pos < 0) { + (void)posBOF(ssFile); + earlyPos = sydSSFFuncSeekStamp(ssFile, &earlyStamp, 0, 0L); + if ((pos = sydSSFFuncSeekStamp(ssFile, &stamp, 0, 0L)) < 0) { + stamp = earlyStamp; + pos = earlyPos; + } + } + if (earlyPos < 0) + return S_syd_ERROR; + if (TsCmpStampsLT(pStamp, &earlyStamp)) { + (void)posBOF(ssFile); + earlyPos = sydSSFFuncSeekStamp(ssFile, &earlyStamp, 0, 0L); + if ((pos = sydSSFFuncSeekStamp(ssFile, &stamp, 0, 0L)) < 0) { + stamp = earlyStamp; + pos = earlyPos; + } + } + if (earlyPos < 0) + return S_syd_ERROR; +/*----------------------------------------------------------------------------- +* now, find the record with the proper time stamp, going forward +*----------------------------------------------------------------------------*/ + while (1) { + if (TsCmpStampsLE(pStamp, &earlyStamp)) + break; + else if (TsCmpStampsLE(pStamp, &stamp) && + TsCmpStampsGE(pStamp, &earlyStamp)) { + if (TsCmpStampsEQ(pStamp, &earlyStamp)) { + stamp = earlyStamp; + pos = earlyPos; + } + break; + } + else if (TsCmpStampsEQ(&earlyStamp, &stamp)) + break; + else if (TsCmpStampsGT(pStamp, &stamp)) { + earlyStamp = stamp; + earlyPos = pos; + pos = sydSSFFuncSeekStamp(ssFile, &stamp, 0, earlyPos); + if (pos < 0) { + stamp = earlyStamp; + pos = earlyPos; + } + } + } +/*----------------------------------------------------------------------------- +* finally, position the file at the proper record +*----------------------------------------------------------------------------*/ + if (TsCmpStampsGT(pStamp, &stamp)) + return S_syd_EOF; + if (TsCmpStampsLE(pStamp, &earlyStamp)) + (void)posPos(ssFile, earlyPos); + else + (void)posPos(ssFile, pos); + } + else if (funcCode == SYD_FC_CLOSE) { + ; /* no action to close a channel in sample set files */ + } + else if (funcCode == SYD_FC_FILEINFO) { + TS_STAMP oldestStamp; + char oldestStampText[28]; + TS_STAMP newestStamp; + char newestStampText[28]; + int j; + + if (posBOF(ssFile) < 0) { + (void)fprintf((FILE *)pArg, "error positioning file\n"); + return S_syd_ERROR; + } + if (sydSSFFuncSeekStamp(ssFile, &oldestStamp, 0, 0L) < 0) { + (void)fprintf((FILE *)pArg, "error finding oldest time stamp\n"); + return S_syd_ERROR; + } + if (sydSSFFuncSeekStamp(ssFile, &newestStamp, 1, -1L) < 0) { + (void)fprintf((FILE *)pArg, "error finding newest time stamp\n"); + return S_syd_ERROR; + } + (void)fprintf((FILE *)pArg, "data from %s through %s\n", + tsStampToText(&oldestStamp, TS_TEXT_MMDDYY, oldestStampText), + tsStampToText(&newestStamp, TS_TEXT_MMDDYY, newestStampText) ); + if (posBOF(ssFile) < 0) { + (void)fprintf((FILE *)pArg, "error positioning file\n"); + return S_syd_ERROR; + } + if (posKeyFwd("%endHeader%", ssFile) < 0) { + (void)fprintf((FILE *)pArg, + "error locating %%endHeader%% in file\n"); + return S_syd_ERROR; + } + (void)fgets(record, 120, ssFile); /* skip over endHeader */ + while (1) { + if (fgets(record, 120, ssFile) == NULL) { + (void)fprintf((FILE *)pArg, "error reading channel name\n"); + return S_syd_ERROR; + } + pRecord = record; + recDelim = record[strlen(record)-1]; + if (nextChanNameField(&pRecord, &pField, &delim) <= 1) { + (void)fprintf((FILE *)pArg, "error reading channel type\n"); + return S_syd_ERROR; + } + if (strcmp(pField, "%endData%") == 0) + break; + (void)strcpy(chanName, pField); + if (nextAlphField(&pRecord, &pField, &delim) <= 1) { + (void)fprintf((FILE *)pArg, "error reading channel type\n"); + return S_syd_ERROR; + } + if (sscanf(pRecord, "%*d %*d %d", &elCount) != 1) { + (void)fprintf((FILE *)pArg, "error reading element count\n"); + return S_syd_ERROR; + } + j = strlen(chanName); + if (j > 4) { + if (strncmp(&chanName[j-4], ".VAL", 4) == 0) + chanName[j-4] = '\0'; + } + + (void)fprintf((FILE *)pArg, "%s %d %s\n", + chanName, elCount, pField); + if (recDelim != '\n') { + while ((delim = fgetc(ssFile)) != EOF && delim != '\n') + ; /* keep skipping until end of line */ + } + } + if (posBOF(ssFile) < 0) { + (void)fprintf((FILE *)pArg, "error positioning file\n"); + return S_syd_ERROR; + } + } + else if (funcCode == SYD_FC_WRAPUP) { + if (ssFile != NULL) + (void)close(ssFile); + pSspec->pHandle = NULL; + } + + return retStat; +} + +long +sydSSFFuncReadData(pSChan, ssFile, bufStat, bufNum, oldBufNum) +SYD_CHAN *pSChan; /* pointer to syncSet channel descriptor */ +FILE *ssFile; +int bufStat, bufNum, oldBufNum; +{ + long stat; + int alStat, alSev, elCount; + int i; + +/*----------------------------------------------------------------------------- +* first, get and store the alarm status and severity . +* (This is done using the "float" form of the buffer, which is +* possible because for these items all types of buffers look the same.) +*----------------------------------------------------------------------------*/ + stat = fscanf(ssFile, "%d%d%d", &alSev, &alStat, &elCount); + assert(stat == 3); + pSChan->pInBuf[bufNum]->tfltval.status = alStat; + pSChan->pInBuf[bufNum]->tfltval.severity = alSev; +/*----------------------------------------------------------------------------- +* now get the actual data +*----------------------------------------------------------------------------*/ + if (pSChan->dbrType == DBR_TIME_FLOAT) { + float *pFl; + pFl = &pSChan->pInBuf[bufNum]->tfltval.value; + for (i=0; idbrType == DBR_TIME_SHORT) { + short *pSh; + pSh = &pSChan->pInBuf[bufNum]->tshrtval.value; + for (i=0; idbrType == DBR_TIME_DOUBLE) { + double *pDbl; + pDbl = &pSChan->pInBuf[bufNum]->tdblval.value; + for (i=0; idbrType == DBR_TIME_LONG) { + long *pL; + pL = &pSChan->pInBuf[bufNum]->tlngval.value; + for (i=0; idbrType == DBR_TIME_STRING) { + char *pC; + int nChar; + pC = pSChan->pInBuf[bufNum]->tstrval.value; + for (i=0; i 0 && nChar < db_strval_dim); + if (fgets(pC, nChar, ssFile) == NULL) + assertAlways(0); + pC += db_strval_dim; + } + } + else if (pSChan->dbrType == DBR_TIME_CHAR) { + unsigned char *pC; + pC = &pSChan->pInBuf[bufNum]->tchrval.value; + for (i=0; idbrType == DBR_TIME_ENUM) { + short *pSh; + char state[80]; + int iState; + pSh = &pSChan->pInBuf[bufNum]->tenmval.value; + for (i=0; i= pSChan->grBuf.genmval.no_str) { + pSChan->grBuf.genmval.no_str++; + (void)strcpy(pSChan->grBuf.genmval.strs[iState], state); + break; + } + else if (strcmp(pSChan->grBuf.genmval.strs[iState],state) == 0) + break; + iState++; + assert(iState < db_state_dim); + } + *pSh++ = iState; + } + } +/*----------------------------------------------------------------------------- +* set the buffer status. If the previous record had a status of MISSING, +* then the buffer status will be SYD_B_RESTART; otherwise, the caller's +* status will be used. +*----------------------------------------------------------------------------*/ + if (oldBufNum >= 0 && pSChan->inStatus[oldBufNum] == SYD_B_MISSING) + pSChan->inStatus[bufNum] = SYD_B_RESTART; + else + pSChan->inStatus[bufNum] = bufStat; + + return S_syd_OK; +} + +static long +sydSSFFuncGetGR(pSspec, pSChan) +SYD_SPEC *pSspec; /* pointer to syncSet specification */ +SYD_CHAN *pSChan; /* pointer to syncSet channel descriptor */ +{ + int i; + + if (pSChan->dbrType == DBR_TIME_FLOAT) { +#define FLT_DEST pSChan->grBuf.gfltval + FLT_DEST.status = NO_ALARM; + FLT_DEST.severity = NO_ALARM; +#if 1 + FLT_DEST.precision = 3; + (void)strcpy(FLT_DEST.units, " "); + FLT_DEST.upper_disp_limit = 0.; + FLT_DEST.lower_disp_limit = 0.; + FLT_DEST.upper_alarm_limit = 0.; + FLT_DEST.lower_alarm_limit = 0.; + FLT_DEST.upper_warning_limit = 0.; + FLT_DEST.lower_warning_limit = 0.; +#else +#define FLT_SRC ArCDChanHdr(pChanDesc).graphics.floatGr + (void)strcpy(FLT_DEST.units, FLT_SRC.units); + FLT_DEST.upper_disp_limit = FLT_SRC.upDispLim; + FLT_DEST.lower_disp_limit = FLT_SRC.lowDispLim; + FLT_DEST.upper_alarm_limit = FLT_SRC.upAlmLim; + FLT_DEST.lower_alarm_limit = FLT_SRC.lowAlmLim; + FLT_DEST.upper_warning_limit = FLT_SRC.upWarnLim; + FLT_DEST.lower_warning_limit = FLT_SRC.lowWarnLim; +#endif + } + else if (pSChan->dbrType == DBR_TIME_SHORT) { +#define SHRT_DEST pSChan->grBuf.gshrtval + SHRT_DEST.status = NO_ALARM; + SHRT_DEST.severity = NO_ALARM; +#if 1 + (void)strcpy(SHRT_DEST.units, " "); + SHRT_DEST.upper_disp_limit = 0; + SHRT_DEST.lower_disp_limit = 0; + SHRT_DEST.upper_alarm_limit = 0; + SHRT_DEST.lower_alarm_limit = 0; + SHRT_DEST.upper_warning_limit = 0; + SHRT_DEST.lower_warning_limit = 0; +#else +#define SHRT_SRC ArCDChanHdr(pChanDesc).graphics.shortGr + (void)strcpy(SHRT_DEST.units, SHRT_SRC.units); + SHRT_DEST.upper_disp_limit = SHRT_SRC.upDispLim; + SHRT_DEST.lower_disp_limit = SHRT_SRC.lowDispLim; + SHRT_DEST.upper_alarm_limit = SHRT_SRC.upAlmLim; + SHRT_DEST.lower_alarm_limit = SHRT_SRC.lowAlmLim; + SHRT_DEST.upper_warning_limit = SHRT_SRC.upWarnLim; + SHRT_DEST.lower_warning_limit = SHRT_SRC.lowWarnLim; +#endif + } + else if (pSChan->dbrType == DBR_TIME_DOUBLE) { +#define DBL_DEST pSChan->grBuf.gdblval + DBL_DEST.status = NO_ALARM; + DBL_DEST.severity = NO_ALARM; +#if 1 + DBL_DEST.precision = 3; + (void)strcpy(DBL_DEST.units, " "); + DBL_DEST.upper_disp_limit = 0.; + DBL_DEST.lower_disp_limit = 0.; + DBL_DEST.upper_alarm_limit = 0.; + DBL_DEST.lower_alarm_limit = 0.; + DBL_DEST.upper_warning_limit = 0.; + DBL_DEST.lower_warning_limit = 0.; +#else +#define DBL_SRC ArCDChanHdr(pChanDesc).graphics.doubleGr + (void)strcpy(DBL_DEST.units, DBL_SRC.units); + DBL_DEST.upper_disp_limit = DBL_SRC.upDispLim; + DBL_DEST.lower_disp_limit = DBL_SRC.lowDispLim; + DBL_DEST.upper_alarm_limit = DBL_SRC.upAlmLim; + DBL_DEST.lower_alarm_limit = DBL_SRC.lowAlmLim; + DBL_DEST.upper_warning_limit = DBL_SRC.upWarnLim; + DBL_DEST.lower_warning_limit = DBL_SRC.lowWarnLim; +#endif + } + else if (pSChan->dbrType == DBR_TIME_LONG) { +#define LNG_DEST pSChan->grBuf.glngval + LNG_DEST.status = NO_ALARM; + LNG_DEST.severity = NO_ALARM; +#if 1 + (void)strcpy(LNG_DEST.units, " "); + LNG_DEST.upper_disp_limit = 0; + LNG_DEST.lower_disp_limit = 0; + LNG_DEST.upper_alarm_limit = 0; + LNG_DEST.lower_alarm_limit = 0; + LNG_DEST.upper_warning_limit = 0; + LNG_DEST.lower_warning_limit = 0; +#else +#define LNG_SRC ArCDChanHdr(pChanDesc).graphics.longGr + (void)strcpy(LNG_DEST.units, LNG_SRC.units); + LNG_DEST.upper_disp_limit = LNG_SRC.upDispLim; + LNG_DEST.lower_disp_limit = LNG_SRC.lowDispLim; + LNG_DEST.upper_alarm_limit = LNG_SRC.upAlmLim; + LNG_DEST.lower_alarm_limit = LNG_SRC.lowAlmLim; + LNG_DEST.upper_warning_limit = LNG_SRC.upWarnLim; + LNG_DEST.lower_warning_limit = LNG_SRC.lowWarnLim; +#endif + } + else if (pSChan->dbrType == DBR_TIME_STRING) { +#define STR_DEST pSChan->grBuf.gstrval + STR_DEST.status = NO_ALARM; + STR_DEST.severity = NO_ALARM; + } + else if (pSChan->dbrType == DBR_TIME_ENUM) { +#define ENM_DEST pSChan->grBuf.genmval + ENM_DEST.status = NO_ALARM; + ENM_DEST.severity = NO_ALARM; +#if 1 + ENM_DEST.no_str = 0; +#else +#define ENM_SRC ArCDChanHdr(pChanDesc).graphics.enumGr + ENM_DEST.no_str = ArCFChanNStates(pChanDesc); + for (i=0; idbrType == DBR_TIME_CHAR) { +#define CHR_DEST pSChan->grBuf.gchrval + CHR_DEST.status = NO_ALARM; + CHR_DEST.severity = NO_ALARM; + (void)strcpy(CHR_DEST.units, " "); +#if 1 + CHR_DEST.upper_disp_limit = 0; + CHR_DEST.lower_disp_limit = 0; + CHR_DEST.upper_alarm_limit = 0; + CHR_DEST.lower_alarm_limit = 0; + CHR_DEST.upper_warning_limit = 0; + CHR_DEST.lower_warning_limit = 0; +#else +#define CHR_SRC ArCDChanHdr(pChanDesc).graphics.charGr + (void)strcpy(CHR_DEST.units, CHR_SRC.units); + CHR_DEST.upper_disp_limit = CHR_SRC.upDispLim; + CHR_DEST.lower_disp_limit = CHR_SRC.lowDispLim; + CHR_DEST.upper_alarm_limit = CHR_SRC.upAlmLim; + CHR_DEST.lower_alarm_limit = CHR_SRC.lowAlmLim; + CHR_DEST.upper_warning_limit = CHR_SRC.upWarnLim; + CHR_DEST.lower_warning_limit = CHR_SRC.lowWarnLim; +#endif + } + return S_syd_OK; +} + +long +sydSSFFuncSeekStamp(ssFile, pStamp, revFlag, pos) +FILE *ssFile; +TS_STAMP *pStamp; +int revFlag; /* I 1 says seek in reverse direction */ +long pos; /* I position to start search if reverse; -1 + says to start at EOF */ +{ + long bufStat; + char record[120], recDelim; + char *pRecord, *pField, delim; + long stat; + + if (revFlag) + pos = posKeyRev("SAMPLE: at", ssFile, pos); + else + pos = posKeyFwd("SAMPLE: at", ssFile); + if (pos < 0) + return pos; + assert(sizeof(record) == 120); + if (fgets(record, 120, ssFile) == NULL) + return -1; + + pRecord = record; + recDelim = record[strlen(record)-1]; + if (nextNonSpaceField(&pRecord, &pField, &delim) <= 1) + assertAlways(0); + if (strcmp(pField, "SAMPLE:") != 0) + assertAlways(0); + if (nextNonSpaceField(&pRecord, &pField, &delim) <= 1) + assertAlways(0); + if (strcmp(pField, "at") != 0) + assertAlways(0); + stat = tsTextToStamp(pStamp, &pRecord); + assert(stat == S_ts_OK); + if (revFlag) { + if (posPos(ssFile, pos) < 0) + assertAlways(0); + } + return pos; +} + +long +sydSSFFuncSeekSample(ssFile, pStamp) +FILE *ssFile; +TS_STAMP *pStamp; +{ + long pos; + long bufStat; + char record[120], recDelim; + char *pRecord, *pField, delim; + long stat; + + if ((pos = posKeyFwd("SAMPLE: at", ssFile)) < 0) + return SYD_B_EOF; + assert(sizeof(record) == 120); + if (fgets(record, 120, ssFile) == NULL) + return SYD_B_EOF; + + pRecord = record; + recDelim = record[strlen(record)-1]; + if (nextNonSpaceField(&pRecord, &pField, &delim) <= 1) + assertAlways(0); + if (strcmp(pField, "SAMPLE:") != 0) + assertAlways(0); + if (nextNonSpaceField(&pRecord, &pField, &delim) <= 1) + assertAlways(0); + if (strcmp(pField, "at") != 0) + assertAlways(0); + stat = tsTextToStamp(pStamp, &pRecord); + assert(stat == S_ts_OK); + while (*pRecord == '-') + pRecord++; + if (strncmp(pRecord, "initial", 7) == 0) + bufStat = SYD_B_RESTART; + else + bufStat = SYD_B_FULL; + if (recDelim != '\n') { + while ((delim = fgetc(ssFile)) != EOF && delim != '\n') + ; /* keep skipping until end of line */ + } + if (posKeyFwd("%endHeader%", ssFile) < 0) + return SYD_B_EOF; + else if (fgets(record, 120, ssFile) == NULL) + return SYD_B_EOF; + return bufStat; +} diff --git a/src/libCom/tsSubr.c b/src/libCom/tsSubr.c new file mode 100644 index 000000000..25286ff2e --- /dev/null +++ b/src/libCom/tsSubr.c @@ -0,0 +1,1227 @@ +/* $Id$ + * Author: Roger A. Cole + * Date: 08-09-90 + * + * Experimental Physics and Industrial Control System (EPICS) + * + * Copyright 1991, 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 + * + * 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 +* 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 ) +* 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. +* +* 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 +# include +# include +# include +#elif VMS +#else +# include +# include +# include +# include +# include +#endif + +#ifndef INC_genDefs_h +# include /* provides assert() */ +#endif +#define TS_TEXT_GLBLSOURCE +#include + +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 0 || LINT /* change to #if 1 to invoke the test code */ + +# 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 + +/*+/subr********************************************************************** +* NAME date - print present time and date +* +* DESCRIPTION +* Prints the present date and time when running under VxWorks. +* +* RETURNS +* void +* +*-*/ +#ifdef vxWorks +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. +* +*-*/ +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 +* +*-*/ +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.) +* +*-*/ + +/*+/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. +* +*-*/ +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 + extern TS_STAMP time_stamp; /* VxWorks global time stamp */ + int prevIntLevel; + + assert(pStamp != NULL); + prevIntLevel = intLock(); + *pStamp = time_stamp; + intUnlock(prevIntLevel); +# endif +#elif 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 + + pStamp->nsec = pStamp->nsec - (pStamp->nsec % TS_TRUNC); + return retStat; +} + +void tsStampToLocalZone(); +/*+/internal****************************************************************** +* NAME tsStampToLocal - convert time stamp to local time +* +* DESCRIPTION +* Converts a GTACS 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 +* +*-*/ +void +tsStampToLocal(stamp, pT) +TS_STAMP stamp; /* I GTACS 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; +} + + +void +tsStampToLocalZone(pStamp, pT) +TS_STAMP *pStamp;/* pointer to GTACS 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 */ + + assert(pStamp != NULL); + assert(pStamp->secPastEpoch >= TS_MIN_WEST * 60); + assert(pT != NULL); + +/*---------------------------------------------------------------------------- +* move the time stamp from GMT to local time, then break it down into its +* component parts +*----------------------------------------------------------------------------*/ + secPastEpoch = pStamp->secPastEpoch - TS_MIN_WEST * 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; + 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 GTACS 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". +* +*-*/ +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 a GTACS 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 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 (index(*pText, ',') != NULL) { /* 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 (index(*pText, '-') != NULL) { /* 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 + 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; + } + } + +/*---------------------------------------------------------------------------- +* convert tsDetail structure into a time stamp, after doing a little more +* refinement of the tsDetail structure +*----------------------------------------------------------------------------*/ + stamp.secPastEpoch = 0; + stamp.nsec = nsec; + if (retStat == S_ts_OK) { + year = TS_EPOCH_YEAR; + days = t.dayYear; + while (year < t.year) { + if (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)) + days += 366; + else + days += 365; + year++; + } + t.dayOfWeek = (days + TS_EPOCH_WDAY_NUM) % 7; + stamp.secPastEpoch = days * 86400; /* converted to seconds */ + stamp.secPastEpoch += t.hours*3600 + t.minutes*60 + t.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, t.leapYear, t.dayYear, t.dayOfWeek); + dstEnd = sunday(TS_DST_END, t.leapYear, t.dayYear, t.dayOfWeek); + assert(dstBegin != dstEnd); + dst = 0; + if (dstBegin < dstEnd && (t.dayYear < dstBegin || t.dayYear > dstEnd)) + ; /* not DST; no action */ + else if (dstBegin > dstEnd && t.dayYear < dstBegin && t.dayYear>dstEnd) + ; /* not DST; no action */ + else if (t.dayYear == dstBegin) { + if (t.hours < TS_DST_HOUR_ON) + ; /* not DST; no action */ + else if (t.hours < TS_DST_HOUR_ON + TS_DST_HRS_ADD) + retStat = S_ts_timeSkippedDST; + else + dst = 1; + } + else if (t.dayYear != dstEnd) + dst = 1; + else { + if (t.hours >= TS_DST_HOUR_OFF + TS_DST_HRS_ADD) + ; /* not DST */ + else if (t.hours < TS_DST_HOUR_OFF) + dst = 1; + else if (t.dstOverlapChar == 'd') + dst = 1; + } + if (dst) + stamp.secPastEpoch -= TS_DST_HRS_ADD * 3600; + stamp.secPastEpoch += TS_MIN_WEST*60; + } + + *pStamp = stamp; + return retStat; +} + +/*+/subr********************************************************************** +* NAME tsTimeTextToStamp - convert text time into a time stamp +* +* DESCRIPTION +* A text string is scanned to produce a GTACS 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.) +* +* RETURNS +* S_ts_OK, or +* S_ts_inputTextError if the text isn't in a legal form +* +*-*/ +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; +}