/* $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 * for the EPOCH year * * 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 ) * 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 # include # include # include #elif VMS #else # include # include # include # include # include #endif #ifndef INC_genDefs_h # include /* provides assert() */ #endif #include #define TS_PRIVATE_DATA #include 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 extern TS_STAMP time_stamp; /* VxWorks global time stamp */ int key; assert(pStamp != NULL); key = intLock(); *pStamp = time_stamp; intUnlock(key); # 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; } /*+/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 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; 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; }