diff --git a/modules/libcom/src/osi/epicsTime.cpp b/modules/libcom/src/osi/epicsTime.cpp index 044d58cfa..90d4f9810 100644 --- a/modules/libcom/src/osi/epicsTime.cpp +++ b/modules/libcom/src/osi/epicsTime.cpp @@ -12,13 +12,6 @@ /* epicsTime.cpp */ /* Author Jeffrey O. Hill */ -// Notes: -// 1) The epicsTime::nSec field is not public and so it could be -// changed to work more like the fractional seconds field in the NTP time -// stamp. That would significantly improve the precision of epicsTime on -// 64 bit architectures. -// - #include #include @@ -29,480 +22,76 @@ #include #include // vxWorks 6.0 requires this include -#include "locationException.h" +#include "errSymTbl.h" #include "epicsAssert.h" #include "epicsVersion.h" +#include "epicsStdlib.h" +#include "epicsMath.h" #include "envDefs.h" #include "epicsTime.h" #include "osiSock.h" /* pull in struct timeval */ #include "epicsStdio.h" -static const char pEpicsTimeVersion[] = - "@(#) " EPICS_VERSION_STRING ", Common Utilities Library"; - -// -// useful public constants -// -static const unsigned mSecPerSec = 1000u; -static const unsigned uSecPerMSec = 1000u; -static const unsigned uSecPerSec = uSecPerMSec * mSecPerSec; -static const unsigned nSecPerUSec = 1000u; -static const unsigned nSecPerSec = nSecPerUSec * uSecPerSec; +static const epicsUInt32 nSecPerSec = 1000000000u; static const unsigned nSecFracDigits = 9u; -// Timescale conversion data - -static const unsigned long NTP_TIME_AT_POSIX_EPOCH = 2208988800ul; -static const unsigned long NTP_TIME_AT_EPICS_EPOCH = - NTP_TIME_AT_POSIX_EPOCH + POSIX_TIME_AT_EPICS_EPOCH; - -// -// epicsTime (const unsigned long secIn, const unsigned long nSecIn) -// -inline epicsTime::epicsTime (const unsigned long secIn, const unsigned long nSecIn) : - secPastEpoch ( secIn ), nSec ( nSecIn ) +void epicsTime::throwError(int code) { - if (nSecIn >= nSecPerSec) { - this->secPastEpoch += nSecIn / nSecPerSec; - this->nSec = nSecIn % nSecPerSec; - } -} - -// -// epicsTimeLoadTimeInit -// -class epicsTimeLoadTimeInit { -public: - epicsTimeLoadTimeInit (); - double epicsEpochOffset; // seconds - double time_tSecPerTick; // seconds (both NTP and EPICS use int sec) - unsigned long epicsEpochOffsetAsAnUnsignedLong; - bool useDiffTimeOptimization; -}; - -// -// epicsTimeLoadTimeInit () -// -epicsTimeLoadTimeInit::epicsTimeLoadTimeInit () -{ - // All we know about time_t is that it is an arithmetic type. - time_t t_zero = static_cast (0); - time_t t_one = static_cast (1); - this->time_tSecPerTick = difftime (t_one, t_zero); - - /* The EPICS epoch (1/1/1990 00:00:00UTC) was 631152000 seconds after - * the ANSI epoch (1/1/1970 00:00:00UTC) - * Convert this offset into time_t units, however this must not be - * calculated using local time (i.e. using mktime() or similar), since - * in the UK the ANSI Epoch had daylight saving time in effect, and - * the value calculated would be 3600 seconds wrong.*/ - this->epicsEpochOffset = - (double) POSIX_TIME_AT_EPICS_EPOCH / this->time_tSecPerTick; - - if (this->time_tSecPerTick == 1.0 && - this->epicsEpochOffset <= ULONG_MAX && - this->epicsEpochOffset >= 0) { - // We can use simpler code on Posix-compliant systems - this->useDiffTimeOptimization = true; - this->epicsEpochOffsetAsAnUnsignedLong = - static_cast(this->epicsEpochOffset); - } else { - // Forced to use the slower but correct code - this->useDiffTimeOptimization = false; - this->epicsEpochOffsetAsAnUnsignedLong = 0; - } -} - -// -// private epicsTime::addNanoSec () -// -// Most formats keep the nSec value as an unsigned long, so are +ve. -// struct timeval's tv_usec may be -1, but I think that means error, -// so this private method never needs to handle -ve offsets. -// -void epicsTime :: addNanoSec ( long nSecAdj ) -{ - if (nSecAdj <= 0) + if(code==epicsTimeOK) return; - - if (static_cast(nSecAdj) >= nSecPerSec) { - this->secPastEpoch += nSecAdj / nSecPerSec; - nSecAdj %= nSecPerSec; - } - - this->nSec += nSecAdj; // Can't overflow - if (this->nSec >= nSecPerSec) { - this->secPastEpoch++; - this->nSec -= nSecPerSec; - } + throw std::logic_error(errSymMsg(code)); } -// -// epicsTime (const time_t_wrapper &tv) -// -epicsTime::epicsTime ( const time_t_wrapper & ansiTimeTicks ) -{ - // avoid c++ static initialization order issues - static epicsTimeLoadTimeInit & lti = * new epicsTimeLoadTimeInit (); - // - // try to directly map time_t into an unsigned long integer because this is - // faster on systems w/o hardware floating point and a simple integer type time_t. - // - if ( lti.useDiffTimeOptimization ) { - // LONG_MAX is used here and not ULONG_MAX because some systems (linux) - // still store time_t as a long. - if ( ansiTimeTicks.ts > 0 && ansiTimeTicks.ts <= LONG_MAX ) { - unsigned long ticks = static_cast < unsigned long > ( ansiTimeTicks.ts ); - if ( ticks >= lti.epicsEpochOffsetAsAnUnsignedLong ) { - this->secPastEpoch = ticks - lti.epicsEpochOffsetAsAnUnsignedLong; - } - else { - this->secPastEpoch = ( ULONG_MAX - lti.epicsEpochOffsetAsAnUnsignedLong ) + ticks; - } - this->nSec = 0; - return; - } - } - - // - // otherwise map time_t, which ANSI C and POSIX define as any arithmetic type, - // into type double - // - double sec = ansiTimeTicks.ts * lti.time_tSecPerTick - lti.epicsEpochOffset; - - // - // map into the the EPICS time stamp range (which allows rollover) - // - static double uLongMax = static_cast (ULONG_MAX); - if ( sec < 0.0 ) { - if ( sec < -uLongMax ) { - sec = sec + static_cast ( -sec / uLongMax ) * uLongMax; - } - sec += uLongMax; - } - else if ( sec > uLongMax ) { - sec = sec - static_cast ( sec / uLongMax ) * uLongMax; - } - - this->secPastEpoch = static_cast ( sec ); - this->nSec = static_cast ( ( sec-this->secPastEpoch ) * nSecPerSec ); +epicsTime::epicsTime ( const epicsTimeStamp & replace ) { + ts = replace; + if(ts.nsec >= nSecPerSec) + throw std::logic_error("epicsTimeStamp has overflow in nano-seconds field"); } -epicsTime::epicsTime (const epicsTimeStamp &ts) -{ - if ( ts.nsec < nSecPerSec ) { - this->secPastEpoch = ts.secPastEpoch; - this->nSec = ts.nsec; - } - else { - throw std::logic_error ( - "epicsTimeStamp has overflow in nano-seconds field" ); - } -} - -epicsTime::epicsTime () : - secPastEpoch(0u), nSec(0u) {} - epicsTime epicsTime::getCurrent () { epicsTimeStamp current; int status = epicsTimeGetCurrent (¤t); if (status) { - throwWithLocation ( unableToFetchCurrentTime () ); + throw unableToFetchCurrentTime ("Unable to fetch Current Time"); } return epicsTime ( current ); } -epicsTime epicsTime::getMonotonic() -{ - epicsTimeStamp current; - epicsTimeGetMonotonic (¤t); // can't fail - return epicsTime ( current ); -} - epicsTime epicsTime::getEvent (const epicsTimeEvent &event) { epicsTimeStamp current; int status = epicsTimeGetEvent (¤t, event); if (status) { - throwWithLocation ( unableToFetchCurrentTime () ); + throw unableToFetchCurrentTime ("Unable to fetch Event Time"); } return epicsTime ( current ); } -// -// operator time_t_wrapper () -// -epicsTime::operator time_t_wrapper () const -{ - // avoid c++ static initialization order issues - static epicsTimeLoadTimeInit & lti = * new epicsTimeLoadTimeInit (); - time_t_wrapper wrap; - - if ( lti.useDiffTimeOptimization ) { - if ( this->secPastEpoch < ULONG_MAX - lti.epicsEpochOffsetAsAnUnsignedLong ) { - wrap.ts = static_cast ( this->secPastEpoch + lti.epicsEpochOffsetAsAnUnsignedLong ); - return wrap; - } - } - - // - // map type double into time_t which ansi C defines as some arithmetic type - // - double tmp = (this->secPastEpoch + lti.epicsEpochOffset) / lti.time_tSecPerTick; - tmp += (this->nSec / lti.time_tSecPerTick) / nSecPerSec; - - wrap.ts = static_cast ( tmp ); - - return wrap; +epicsTime::operator struct timeval () const { + timeval ret; + epicsTimeToTimeval(&ret, &ts); + return ret; } -// -// convert to ANSI C struct tm (with nano seconds) adjusted for the local time zone -// -epicsTime::operator local_tm_nano_sec () const -{ - time_t_wrapper ansiTimeTicks = *this; - - local_tm_nano_sec tm; - - int status = epicsTime_localtime ( &ansiTimeTicks.ts, &tm.ansi_tm ); - if ( status ) { - throw std::logic_error ( "epicsTime_localtime failed" ); - } - - tm.nSec = this->nSec; - - return tm; +epicsTime::epicsTime ( const struct timeval & replace) { + throwError(epicsTimeFromTimeval(&ts, &replace)); } -// -// convert to ANSI C struct tm (with nano seconds) adjusted for UTC -// -epicsTime::operator gm_tm_nano_sec () const -{ - time_t_wrapper ansiTimeTicks = *this; - - gm_tm_nano_sec tm; - - int status = epicsTime_gmtime ( &ansiTimeTicks.ts, &tm.ansi_tm ); - if ( status ) { - throw std::logic_error ( "epicsTime_gmtime failed" ); - } - - tm.nSec = this->nSec; - - return tm; +epicsTime & epicsTime::operator = ( const struct timeval & replace) { + throwError(epicsTimeFromTimeval(&ts, &replace)); + return *this; } -// -// epicsTime (const local_tm_nano_sec &tm) -// -epicsTime::epicsTime (const local_tm_nano_sec &tm) +std::ostream& operator<<(std::ostream& strm, const epicsTime& ts) { - struct tm tmp = tm.ansi_tm; - time_t_wrapper ansiTimeTicks = { mktime (&tmp) }; + char temp[64]; - static const time_t mktimeError = static_cast (-1); - if (ansiTimeTicks.ts == mktimeError) { - throwWithLocation ( formatProblemWithStructTM () ); - } - - *this = epicsTime(ansiTimeTicks); - this->addNanoSec(tm.nSec); -} - -// -// epicsTime (const gm_tm_nano_sec &tm) -// - -// do conversion avoiding the timezone mechanism -static inline int is_leap(int year) -{ - if (year % 400 == 0) - return 1; - if (year % 100 == 0) - return 0; - if (year % 4 == 0) - return 1; - return 0; -} - -static inline int days_from_0(int year) -{ - year--; - return 365 * year + (year / 400) - (year / 100) + (year / 4); -} - -static inline int days_from_1970(int year) -{ - static const int days_from_0_to_1970 = days_from_0(1970); - return days_from_0(year) - days_from_0_to_1970; -} - -static inline int days_from_1jan(int year, int month, int day) -{ - static const int days[2][12] = - { - { 0,31,59,90,120,151,181,212,243,273,304,334}, - { 0,31,60,91,121,152,182,213,244,274,305,335} - }; - return days[is_leap(year)][month-1] + day - 1; -} - -epicsTime::epicsTime (const gm_tm_nano_sec &tm) -{ - int year = tm.ansi_tm.tm_year + 1900; - int month = tm.ansi_tm.tm_mon; - if (month > 11) { - year += month / 12; - month %= 12; - } else if (month < 0) { - int years_diff = (-month + 11) / 12; - year -= years_diff; - month += 12 * years_diff; - } - month++; - - int day = tm.ansi_tm.tm_mday; - int day_of_year = days_from_1jan(year, month, day); - int days_since_epoch = days_from_1970(year) + day_of_year; - - time_t_wrapper ansiTimeTicks; - ansiTimeTicks.ts = ((days_since_epoch - * 24 + tm.ansi_tm.tm_hour) - * 60 + tm.ansi_tm.tm_min) - * 60 + tm.ansi_tm.tm_sec; - - *this = epicsTime(ansiTimeTicks); - this->addNanoSec(tm.nSec); -} - -// -// operator struct timespec () -// -epicsTime::operator struct timespec () const -{ - struct timespec ts; - time_t_wrapper ansiTimeTicks; - - ansiTimeTicks = *this; - ts.tv_sec = ansiTimeTicks.ts; - ts.tv_nsec = static_cast (this->nSec); - return ts; -} - -// -// epicsTime (const struct timespec &ts) -// -epicsTime::epicsTime (const struct timespec &ts) -{ - time_t_wrapper ansiTimeTicks; - - ansiTimeTicks.ts = ts.tv_sec; - *this = epicsTime (ansiTimeTicks); - this->addNanoSec (ts.tv_nsec); -} - -// -// operator struct timeval () -// -epicsTime::operator struct timeval () const -{ - struct timeval ts; - time_t_wrapper ansiTimeTicks; - - ansiTimeTicks = *this; - // On Posix systems timeval :: tv_sec is a time_t so this can be - // a direct assignment. On other systems I dont know that we can - // guarantee that time_t and timeval :: tv_sec will have the - // same epoch or have the same scaling factor to discrete seconds. - // For example, on windows time_t changed recently to a 64 bit - // quantity but timeval is still a long. That can cause problems - // on 32 bit systems. So technically, we should have an os - // dependent conversion between time_t and timeval :: tv_sec? - ts.tv_sec = ansiTimeTicks.ts; - ts.tv_usec = static_cast < long > ( this->nSec / nSecPerUSec ); - return ts; -} - -// -// epicsTime (const struct timeval &ts) -// -epicsTime::epicsTime (const struct timeval &ts) -{ - time_t_wrapper ansiTimeTicks; - // On Posix systems timeval :: tv_sec is a time_t so this can be - // a direct assignment. On other systems I dont know that we can - // guarantee that time_t and timeval :: tv_sec will have the - // same epoch or have the same scaling factor to discrete seconds. - // For example, on windows time_t changed recently to a 64 bit - // quantity but timeval is still a long. That can cause problems - // on 32 bit systems. So technically, we should have an os - // dependent conversion between time_t and timeval :: tv_sec? - ansiTimeTicks.ts = ts.tv_sec; - *this = epicsTime (ansiTimeTicks); - this->addNanoSec (ts.tv_usec * nSecPerUSec); -} - - -static const double NTP_FRACTION_DENOMINATOR = 1.0 + 0xffffffff; - -struct l_fp { /* NTP time stamp */ - epicsUInt32 l_ui; /* sec past NTP epoch */ - epicsUInt32 l_uf; /* fractional seconds */ -}; - -// -// epicsTime::l_fp () -// -epicsTime::operator l_fp () const -{ - l_fp ts; - ts.l_ui = this->secPastEpoch + NTP_TIME_AT_EPICS_EPOCH; - ts.l_uf = static_cast < unsigned long > - ( ( this->nSec * NTP_FRACTION_DENOMINATOR ) / nSecPerSec ); - return ts; -} - -// -// epicsTime::epicsTime ( const l_fp & ts ) -// -epicsTime::epicsTime ( const l_fp & ts ) -{ - this->secPastEpoch = ts.l_ui - NTP_TIME_AT_EPICS_EPOCH; - this->nSec = static_cast < unsigned long > - ( ( ts.l_uf / NTP_FRACTION_DENOMINATOR ) * nSecPerSec ); -} - -epicsTime::operator epicsTimeStamp () const -{ - if ( this->nSec >= nSecPerSec ) { - throw std::logic_error ( - "epicsTimeStamp has overflow in nano-seconds field?" ); - } - epicsTimeStamp ts; - // - // truncation by design - // ------------------- - // epicsTime::secPastEpoch is based on ulong and has much greater range - // on 64 bit hosts than the original epicsTimeStamp::secPastEpoch. The - // epicsTimeStamp::secPastEpoch is based on epicsUInt32 so that it will - // match the original network protocol. Of course one can anticipate - // that eventually, a epicsUInt64 based network time stamp will be - // introduced when 64 bit architectures are more ubiquitous. - // - // Truncation usually works fine here because the routines in this code - // that compute time stamp differences and compare time stamps produce - // good results when the operands are on either side of a time stamp - // rollover as long as the difference between the operands does not exceed - // 1/2 of full range. - // - ts.secPastEpoch = static_cast < epicsUInt32 > ( this->secPastEpoch ); - ts.nsec = static_cast < epicsUInt32 > ( this->nSec ); - return ts; + (void)ts.strftime(temp, sizeof(temp), "%Y-%m-%d %H:%M:%S.%09f"); + temp[sizeof(temp)-1u] = '\0'; + return strm<%0f" @@ -522,7 +111,7 @@ static const char * fracFormatFind ( unsigned long & fracFmtWidth ) { assert ( prefixBufLen > 1 ); - unsigned long width = ULONG_MAX; + unsigned long width = 0xffffffff; bool fracFound = false; const char * pAfter = pFormat; const char * pFmt = pFormat; @@ -576,15 +165,14 @@ static const char * fracFormatFind ( // // size_t epicsTime::strftime () // -size_t epicsTime::strftime ( - char * pBuff, size_t bufLength, const char * pFormat ) const +size_t epicsStdCall epicsTimeToStrftime (char *pBuff, size_t bufLength, const char *pFormat, const epicsTimeStamp *pTS) { if ( bufLength == 0u ) { return 0u; } // presume that EPOCH date is an uninitialized time stamp - if ( this->secPastEpoch == 0 && this->nSec == 0u ) { + if ( pTS->secPastEpoch == 0 && pTS->nsec == 0u ) { strncpy ( pBuff, "", bufLength ); pBuff[bufLength-1] = '\0'; return strlen ( pBuff ); @@ -609,9 +197,10 @@ size_t epicsTime::strftime ( } // all but fractional seconds use strftime formatting if ( strftimePrefixBuf[0] != '\0' ) { - local_tm_nano_sec tmns = *this; + tm tm; + (void)epicsTimeToTM(&tm, 0, pTS); size_t strftimeNumChar = :: strftime ( - pBufCur, bufLenLeft, strftimePrefixBuf, & tmns.ansi_tm ); + pBufCur, bufLenLeft, strftimePrefixBuf, &tm ); pBufCur [ strftimeNumChar ] = '\0'; pBufCur += strftimeNumChar; bufLenLeft -= strftimeNumChar; @@ -625,8 +214,9 @@ size_t epicsTime::strftime ( // verify that there are enough chars left for the fractional seconds if ( fracWid < bufLenLeft ) { - local_tm_nano_sec tmns = *this; - if ( tmns.nSec < nSecPerSec ) { + tm tm; + (void)epicsTimeToTM(&tm, 0, pTS); + if ( pTS->nsec < nSecPerSec ) { // divisors for fraction (see below) static const unsigned long div[] = { static_cast < unsigned long > ( 1e9 ), @@ -641,7 +231,7 @@ size_t epicsTime::strftime ( static_cast < unsigned long > ( 1e0 ) }; // round without overflowing into whole seconds - unsigned long frac = tmns.nSec + div[fracWid] / 2; + unsigned long frac = pTS->nsec + div[fracWid] / 2; if (frac >= nSecPerSec) frac = nSecPerSec - 1; // convert nanosecs to integer of correct range @@ -691,449 +281,199 @@ size_t epicsTime::strftime ( // // epicsTime::show (unsigned) // -void epicsTime::show ( unsigned level ) const +void epicsStdCall epicsTimeShow (const epicsTimeStamp *pTS, unsigned level) { char bigBuffer[256]; - size_t numChar = this->strftime ( bigBuffer, sizeof ( bigBuffer ), - "%a %b %d %Y %H:%M:%S.%09f" ); + size_t numChar = epicsTimeToStrftime( bigBuffer, sizeof ( bigBuffer ), + "%a %b %d %Y %H:%M:%S.%09f", pTS ); if ( numChar > 0 ) { printf ( "epicsTime: %s\n", bigBuffer ); } - - if ( level > 1 ) { - // this also suppresses the "defined, but not used" - // warning message - printf ( "epicsTime: revision \"%s\"\n", - pEpicsTimeVersion ); - } - } -// -// epicsTime::operator + (const double &rhs) -// -// rhs has units seconds -// -epicsTime epicsTime::operator + (const double &rhs) const +int epicsStdCall epicsTimeToTime_t (time_t *pDest, const epicsTimeStamp *pSrc) { - unsigned long newSec, newNSec, secOffset, nSecOffset; - double fnsec; + STATIC_ASSERT(sizeof(*pDest) >= sizeof(pSrc->secPastEpoch)); - if (rhs >= 0) { - secOffset = static_cast (rhs); - fnsec = rhs - secOffset; - nSecOffset = static_cast ( (fnsec * nSecPerSec) + 0.5 ); - - newSec = this->secPastEpoch + secOffset; // overflow expected - newNSec = this->nSec + nSecOffset; - if (newNSec >= nSecPerSec) { - newSec++; // overflow expected - newNSec -= nSecPerSec; - } - } - else { - secOffset = static_cast (-rhs); - fnsec = rhs + secOffset; - nSecOffset = static_cast ( (-fnsec * nSecPerSec) + 0.5 ); - - newSec = this->secPastEpoch - secOffset; // underflow expected - if (this->nSec>=nSecOffset) { - newNSec = this->nSec - nSecOffset; - } - else { - // borrow - newSec--; // underflow expected - newNSec = this->nSec + (nSecPerSec - nSecOffset); - } - } - return epicsTime (newSec, newNSec); + // widen to 64-bit to (eventually) accomidate 64-bit time_t + *pDest = epicsUInt64(pSrc->secPastEpoch) + POSIX_TIME_AT_EPICS_EPOCH; + return epicsTimeOK; } -// -// operator - -// -// To make this code robust during timestamp rollover events -// time stamp differences greater than one half full scale are -// interpreted as rollover situations: -// -// when RHS is greater than THIS: -// RHS-THIS > one half full scale => return THIS + (ULONG_MAX-RHS) -// RHS-THIS <= one half full scale => return -(RHS-THIS) -// -// when THIS is greater than or equal to RHS -// THIS-RHS > one half full scale => return -(RHS + (ULONG_MAX-THIS)) -// THIS-RHS <= one half full scale => return THIS-RHS -// -double epicsTime::operator - (const epicsTime &rhs) const +int epicsStdCall epicsTimeFromTime_t (epicsTimeStamp *pDest, time_t src) { - double nSecRes, secRes; - - // - // first compute the difference between the nano-seconds members - // - // nano sec member is not allowed to be greater that 1/2 full scale - // so the unsigned to signed conversion is ok - // - if (this->nSec>=rhs.nSec) { - nSecRes = this->nSec - rhs.nSec; - } - else { - nSecRes = rhs.nSec - this->nSec; - nSecRes = -nSecRes; - } - - // - // next compute the difference between the seconds members - // and invert the sign of the nano seconds result if there - // is a range violation - // - if (this->secPastEpochsecPastEpoch; - if (secRes > ULONG_MAX/2) { - // - // In this situation where the difference is more than - // 68 years assume that the seconds counter has rolled - // over and compute the "wrap around" difference - // - secRes = 1 + (ULONG_MAX-secRes); - nSecRes = -nSecRes; - } - else { - secRes = -secRes; - } - } - else { - secRes = this->secPastEpoch - rhs.secPastEpoch; - if (secRes > ULONG_MAX/2) { - // - // In this situation where the difference is more than - // 68 years assume that the seconds counter has rolled - // over and compute the "wrap around" difference - // - secRes = 1 + (ULONG_MAX-secRes); - secRes = -secRes; - nSecRes = -nSecRes; - } - } - - return secRes + nSecRes/nSecPerSec; + pDest->secPastEpoch = epicsInt64(src) - POSIX_TIME_AT_EPICS_EPOCH; + pDest->nsec = 0; + return epicsTimeOK; } -// -// operator <= -// -bool epicsTime::operator <= (const epicsTime &rhs) const +int epicsStdCall epicsTimeToTM (struct tm *pDest, unsigned long *pNSecDest, const epicsTimeStamp *pSrc) { - bool rc; - - if (this->secPastEpochsecPastEpoch < ULONG_MAX/2) { - // - // In this situation where the difference is less than - // 69 years compute the expected result - // - rc = true; - } - else { - // - // In this situation where the difference is more than - // 69 years assume that the seconds counter has rolled - // over and compute the "wrap around" result - // - rc = false; - } - } - else if (this->secPastEpoch>rhs.secPastEpoch) { - if (this->secPastEpoch-rhs.secPastEpoch < ULONG_MAX/2) { - // - // In this situation where the difference is less than - // 69 years compute the expected result - // - rc = false; - } - else { - // - // In this situation where the difference is more than - // 69 years assume that the seconds counter has rolled - // over and compute the "wrap around" result - // - rc = true; - } - } - else { - if (this->nSec<=rhs.nSec) { - rc = true; - } - else { - rc = false; - } - } - return rc; + time_t temp; + int err; + err = epicsTimeToTime_t(&temp, pSrc); + if(!err) + err = epicsTime_localtime(&temp, pDest); + if(!err && pNSecDest) + *pNSecDest = pSrc->nsec; + return err; } -// -// operator < -// -bool epicsTime::operator < (const epicsTime &rhs) const +int epicsStdCall epicsTimeToGMTM (struct tm *pDest, unsigned long *pNSecDest, const epicsTimeStamp *pSrc) { - bool rc; - - if (this->secPastEpochsecPastEpoch < ULONG_MAX/2) { - // - // In this situation where the difference is less than - // 69 years compute the expected result - // - rc = true; - } - else { - // - // In this situation where the difference is more than - // 69 years assume that the seconds counter has rolled - // over and compute the "wrap around" result - // - rc = false; - } - } - else if (this->secPastEpoch>rhs.secPastEpoch) { - if (this->secPastEpoch-rhs.secPastEpoch < ULONG_MAX/2) { - // - // In this situation where the difference is less than - // 69 years compute the expected result - // - rc = false; - } - else { - // - // In this situation where the difference is more than - // 69 years assume that the seconds counter has rolled - // over and compute the "wrap around" result - // - rc = true; - } - } - else { - if (this->nSecnsec; + return err; } -extern "C" { - // - // ANSI C interface - // - // its too bad that these cant be implemented with inline functions - // at least when running the GNU compiler - // - LIBCOM_API int epicsStdCall epicsTimeToTime_t (time_t *pDest, const epicsTimeStamp *pSrc) - { - try { - time_t_wrapper dst = epicsTime (*pSrc); - *pDest = dst.ts; - } - catch (...) { - return S_time_conversion; - } - return epicsTimeOK; - } - LIBCOM_API int epicsStdCall epicsTimeFromTime_t (epicsTimeStamp *pDest, time_t src) - { - try { - time_t_wrapper dst; - dst.ts = src; - *pDest = epicsTime ( dst ); - } - catch (...) { - return S_time_conversion; - } - return epicsTimeOK; - } - LIBCOM_API int epicsStdCall epicsTimeToTM (struct tm *pDest, unsigned long *pNSecDest, const epicsTimeStamp *pSrc) - { - try { - local_tm_nano_sec tmns = epicsTime (*pSrc); - *pDest = tmns.ansi_tm; - if (pNSecDest) - *pNSecDest = tmns.nSec; - } - catch (...) { - return S_time_conversion; - } - return epicsTimeOK; - } - LIBCOM_API int epicsStdCall epicsTimeToGMTM (struct tm *pDest, unsigned long *pNSecDest, const epicsTimeStamp *pSrc) - { - try { - gm_tm_nano_sec gmtmns = epicsTime (*pSrc); - *pDest = gmtmns.ansi_tm; - if (pNSecDest) - *pNSecDest = gmtmns.nSec; - } - catch (...) { - return S_time_conversion; - } - return epicsTimeOK; - } - LIBCOM_API int epicsStdCall epicsTimeFromTM (epicsTimeStamp *pDest, const struct tm *pSrc, unsigned long nSecSrc) - { - try { - local_tm_nano_sec tmns; - tmns.ansi_tm = *pSrc; - tmns.nSec = nSecSrc; - *pDest = epicsTime (tmns); - } - catch (...) { - return S_time_conversion; - } - return epicsTimeOK; - } - LIBCOM_API int epicsStdCall epicsTimeFromGMTM (epicsTimeStamp *pDest, const struct tm *pSrc, unsigned long nSecSrc) - { - try { - gm_tm_nano_sec tmns; - tmns.ansi_tm = *pSrc; - tmns.nSec = nSecSrc; - *pDest = epicsTime (tmns); - } - catch (...) { - return S_time_conversion; - } - return epicsTimeOK; - } - LIBCOM_API int epicsStdCall epicsTimeToTimespec (struct timespec *pDest, const epicsTimeStamp *pSrc) - { - try { - *pDest = epicsTime (*pSrc); - } - catch (...) { - return S_time_conversion; - } - return epicsTimeOK; - } - LIBCOM_API int epicsStdCall epicsTimeFromTimespec (epicsTimeStamp *pDest, const struct timespec *pSrc) - { - try { - *pDest = epicsTime (*pSrc); - } - catch (...) { - return S_time_conversion; - } - return epicsTimeOK; - } - LIBCOM_API int epicsStdCall epicsTimeToTimeval (struct timeval *pDest, const epicsTimeStamp *pSrc) - { - try { - *pDest = epicsTime (*pSrc); - } - catch (...) { - return S_time_conversion; - } - return epicsTimeOK; - } - LIBCOM_API int epicsStdCall epicsTimeFromTimeval (epicsTimeStamp *pDest, const struct timeval *pSrc) - { - try { - *pDest = epicsTime (*pSrc); - } - catch (...) { - return S_time_conversion; - } - return epicsTimeOK; - } - LIBCOM_API double epicsStdCall epicsTimeDiffInSeconds (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) - { - try { - return epicsTime (*pLeft) - epicsTime (*pRight); - } - catch (...) { - return - DBL_MAX; - } - } - LIBCOM_API void epicsStdCall epicsTimeAddSeconds (epicsTimeStamp *pDest, double seconds) - { - try { - *pDest = epicsTime (*pDest) + seconds; - } - catch ( ... ) { - *pDest = epicsTime (); - } - } - LIBCOM_API int epicsStdCall epicsTimeEqual (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) - { - try { - return epicsTime (*pLeft) == epicsTime (*pRight); - } - catch ( ... ) { - return 0; - } - } - LIBCOM_API int epicsStdCall epicsTimeNotEqual (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) - { - try { - return epicsTime (*pLeft) != epicsTime (*pRight); - } - catch ( ... ) { - return 1; - } - } - LIBCOM_API int epicsStdCall epicsTimeLessThan (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) - { - try { - return epicsTime (*pLeft) < epicsTime (*pRight); - } - catch ( ... ) { - return 0; - } - } - LIBCOM_API int epicsStdCall epicsTimeLessThanEqual (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) - { - try { - return epicsTime (*pLeft) <= epicsTime (*pRight); - } - catch ( ... ) { - return 0; - } - } - LIBCOM_API int epicsStdCall epicsTimeGreaterThan (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) - { - try { - return epicsTime (*pLeft) > epicsTime (*pRight); - } - catch ( ... ) { - return 0; - } - } - LIBCOM_API int epicsStdCall epicsTimeGreaterThanEqual (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) - { - try { - return epicsTime (*pLeft) >= epicsTime (*pRight); - } - catch ( ... ) { - return 0; - } - } - LIBCOM_API size_t epicsStdCall epicsTimeToStrftime (char *pBuff, size_t bufLength, const char *pFormat, const epicsTimeStamp *pTS) - { - try { - return epicsTime(*pTS).strftime (pBuff, bufLength, pFormat); - } - catch ( ... ) { - return 0; - } - } - LIBCOM_API void epicsStdCall epicsTimeShow (const epicsTimeStamp *pTS, unsigned interestLevel) - { - try { - epicsTime(*pTS).show (interestLevel); - } - catch ( ... ) { - printf ( "Invalid epicsTimeStamp\n" ); - } - } +int epicsStdCall epicsTimeFromTM (epicsTimeStamp *pDest, const struct tm *pSrc, unsigned long nSecSrc) +{ + tm temp = *pSrc; // mktime() modifies (at least) tm_wday and tm_yday + time_t tsrc = mktime(&temp); + int err = epicsTimeFromTime_t(pDest, tsrc); + if(!err) + pDest->nsec = nSecSrc; + return err; +} + +#ifdef _WIN32 +# define timegm _mkgmtime + +#elif defined(__rtems__) || defined(vxWorks) + +static +time_t timegm(tm* gtm) +{ + // ugly hack for targets without timegm(tm*), but which have mktime(tm*). + // probably has issues near start/end of DST + + // translate to seconds as if a local time. off by TZ offset + time_t fakelocal = mktime(gtm); + // now use gmtime() which applies the TZ offset again, but with the wrong sign + tm wrongtm; + epicsTime_gmtime(&fakelocal, &wrongtm); + // translate this to seconds + time_t fakex2 = mktime(&wrongtm); + + // tzoffset = fakelocal - fakex2; + + return epicsInt64(fakelocal)*2 - fakex2; +} + +#endif + +int epicsStdCall epicsTimeFromGMTM (epicsTimeStamp *pDest, const struct tm *pSrc, unsigned long nSecSrc) +{ + tm temp = *pSrc; // timegm() may modify (at least) tm_wday and tm_yday + time_t tsrc = timegm(&temp); + int err = epicsTimeFromTime_t(pDest, tsrc); + if(!err) + pDest->nsec = nSecSrc; + return err; +} + +int epicsStdCall epicsTimeToTimespec (struct timespec *pDest, const epicsTimeStamp *pSrc) +{ + int err = epicsTimeToTime_t(&pDest->tv_sec, pSrc); + if(!err) + pDest->tv_nsec = pSrc->nsec; + return err; +} + +int epicsStdCall epicsTimeFromTimespec (epicsTimeStamp *pDest, const struct timespec *pSrc) +{ + int err = epicsTimeFromTime_t(pDest, pSrc->tv_sec); + if(!err) + pDest->nsec = pSrc->tv_nsec; + return err; +} + +int epicsStdCall epicsTimeToTimeval (struct timeval *pDest, const epicsTimeStamp *pSrc) +{ + time_t temp; + int err = epicsTimeToTime_t(&temp, pSrc); + if(!err) { + pDest->tv_sec = temp; // tv_sec is not time_t on windows + pDest->tv_usec = pSrc->nsec/1000u; + } + return err; +} + +int epicsStdCall epicsTimeFromTimeval (epicsTimeStamp *pDest, const struct timeval *pSrc) +{ + int err = epicsTimeFromTime_t(pDest, pSrc->tv_sec); + if(!err) + pDest->nsec = pSrc->tv_usec*1000u; + return err; +} + +double epicsStdCall epicsTimeDiffInSeconds (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) +{ + /* double(*pLeft - *pRight) + * + * 0xffffffff*1000000000 < 2**62 + * 0x200000000*1000000002 < 2**63 + * so there is (just barely) space to add 2 TSs as signed 64-bit integers without overflow + */ + + // handle over/underflow as u32 when subtracting + epicsInt64 nsec = epicsInt32(pLeft->secPastEpoch - pRight->secPastEpoch); + nsec *= nSecPerSec; + nsec += epicsInt32(pLeft->nsec) - epicsInt32(pRight->nsec); + + return double(nsec)*1e-9; +} + +void epicsStdCall epicsTimeAddSeconds (epicsTimeStamp *pDest, double seconds) +{ + epicsInt64 nsec = pDest->secPastEpoch; + nsec *= nSecPerSec; + nsec += epicsInt64(pDest->nsec); + nsec += epicsInt64(seconds*1e9 + (seconds>=0.0 ? 0.5 : -0.5)); + pDest->secPastEpoch = nsec/nSecPerSec; + pDest->nsec = (nsec>=0 ? nsec : -nsec)%nSecPerSec; +} + +int epicsStdCall epicsTimeEqual (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) +{ + return pLeft->secPastEpoch == pRight->secPastEpoch && pLeft->nsec == pRight->nsec; +} + +int epicsStdCall epicsTimeNotEqual (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) +{ + return !epicsTimeEqual(pLeft, pRight); +} + +epicsInt64 epicsStdCall epicsTimeDiffInNS (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) +{ + epicsInt64 delta = epicsInt64(pLeft->secPastEpoch) - pRight->secPastEpoch; + delta *= nSecPerSec; + delta += epicsInt64(pLeft->nsec) - pRight->nsec; + return delta; +} + +int epicsStdCall epicsTimeLessThan (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) +{ + return epicsTimeDiffInNS(pLeft, pRight) < 0; +} + +int epicsStdCall epicsTimeLessThanEqual (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) +{ + return epicsTimeDiffInNS(pLeft, pRight) <= 0; +} + +int epicsStdCall epicsTimeGreaterThan (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) +{ + return epicsTimeDiffInNS(pLeft, pRight) > 0; +} + +int epicsStdCall epicsTimeGreaterThanEqual (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight) +{ + return epicsTimeDiffInNS(pLeft, pRight) >= 0; } diff --git a/modules/libcom/src/osi/epicsTime.h b/modules/libcom/src/osi/epicsTime.h index 090fb5c48..8996c3733 100644 --- a/modules/libcom/src/osi/epicsTime.h +++ b/modules/libcom/src/osi/epicsTime.h @@ -26,6 +26,14 @@ /** \brief The EPICS Epoch is 00:00:00 Jan 1, 1990 UTC */ #define POSIX_TIME_AT_EPICS_EPOCH 631152000u +#ifdef __cplusplus + +#include +#include + +extern "C" { +#endif + /** \brief EPICS time stamp, for use from C code. * * Because it uses an unsigned 32-bit integer to hold the seconds count, an @@ -65,277 +73,6 @@ struct timespec; /* POSIX real time */ */ struct timeval; /* BSD */ -/** \struct l_fp - * \brief Network Time Protocol timestamp - * - * Network Time Protocol timestamp. The fields are: - * \li \c lui - Number of seconds since 1900 (The NTP epoch) - * \li \c luf - Fraction of a second. For example 0x800000000 represents 1/2 second. - */ -struct l_fp; /* NTP timestamp */ - -#ifdef __cplusplus - -/** \brief C++ only ANSI C struct tm with nanoseconds, local timezone - * - * Extend ANSI C "struct tm" to include nano seconds within a second - * and a struct tm that is adjusted for the local timezone. - */ -struct local_tm_nano_sec { - struct tm ansi_tm; /**< \brief ANSI C time details */ - unsigned long nSec; /**< \brief nanoseconds extension */ -}; - -/** \brief C++ only ANSI C sruct tm with nanoseconds, UTC - * - * Extend ANSI C "struct tm" to include nanoseconds within a second - * and a struct tm that is adjusted for GMT (UTC). - */ -struct gm_tm_nano_sec { - struct tm ansi_tm; /**< \brief ANSI C time details */ - unsigned long nSec; /**< \brief nanoseconds extension */ -}; - -/** \brief C++ only ANSI C time_t - * - * This is for converting to/from the ANSI C \c time_t. Since \c time_t - * is usually an elementary type providing a conversion operator from - * \c time_t to/from epicsTime could cause undesirable implicit - * conversions. Providing a conversion operator to/from the - * \c time_t_wrapper instead prevents implicit conversions. - */ -struct time_t_wrapper { - time_t ts; -}; - -/** \brief C++ Event number wrapper class - * - * Stores an event number for use by the epicsTime::getEvent() static - * class method. - */ -class LIBCOM_API epicsTimeEvent -{ -public: - epicsTimeEvent (const int &number); /**< \brief Constructor */ - operator int () const; /**< \brief Extractor */ -private: - int eventNumber; -}; - -/** \brief C++ time stamp object - * - * Holds an EPICS time stamp, and provides conversion functions for both - * input and output from/to other types. - * - * \note Time conversions: The epicsTime implementation will properly - * convert between the various formats from the beginning of the EPICS - * epoch until at least 2038. Unless the underlying architecture support - * has defective POSIX, BSD/SRV5, or standard C time support the EPICS - * implementation should be valid until 2106. - */ -class LIBCOM_API epicsTime -{ -public: - /// \brief Exception: Time provider problem - class unableToFetchCurrentTime {}; - /// \brief Exception: Bad field(s) in struct tm - class formatProblemWithStructTM {}; - - /** \brief The default constructor sets the time to the EPICS epoch. */ - epicsTime (); - - /** \brief Get time of event system event. - * - * Returns an epicsTime indicating when the associated event system - * event last occurred. - */ - static epicsTime getEvent ( const epicsTimeEvent & ); - /** \brief Get current clock time - * - * Returns an epicsTime containing the current time. For example: - * \code{.cpp} - * epicsTime now = epicsTime::getCurrent(); - * \endcode - */ - static epicsTime getCurrent (); - /** \brief Get current monotonic time - * - * Returns an epicsTime containing the current monotonic time, an - * OS clock which never going backwards or jumping forwards. - * This time is has an undefined epoch, and is only useful for - * measuring time differences. - */ - static epicsTime getMonotonic (); - - /** \name epicsTimeStamp conversions - * Convert to and from EPICS epicsTimeStamp format - * @{ */ - /** \brief Convert to epicsTimeStamp */ - operator epicsTimeStamp () const; - /** \brief Construct from epicsTimeStamp */ - epicsTime ( const epicsTimeStamp & ts ); - /** \brief Assign from epicsTimeStamp */ - epicsTime & operator = ( const epicsTimeStamp & ); - /** @} */ - - /** \name ANSI C time_t conversions - * Convert to and from ANSI C \c time_t wrapper . - * @{ */ - /** \brief Convert to ANSI C \c time_t */ - operator time_t_wrapper () const; - /** \brief Construct from ANSI C \c time_t */ - epicsTime ( const time_t_wrapper & ); - /** \brief Assign from ANSI C \c time_t */ - epicsTime & operator = ( const time_t_wrapper & ); - /** @} */ - - /** \name ANSI C struct tm local-time conversions - * Convert to and from ANSI Cs struct tm (with nano seconds), - * adjusted for the local time zone. - * @{ */ - /** \brief Convert to struct tm in local time zone */ - operator local_tm_nano_sec () const; - /** \brief Construct from struct tm in local time zone */ - epicsTime ( const local_tm_nano_sec & ); - /** \brief Assign from struct tm in local time zone */ - epicsTime & operator = ( const local_tm_nano_sec & ); - /** @} */ - - /** \name ANSI C struct tm UTC conversions - * Convert to and from ANSI Cs struct tm (with nano seconds), - * adjusted for Greenwich Mean Time (UTC). - * @{ */ - /** \brief Convert to struct tm in UTC/GMT */ - operator gm_tm_nano_sec () const; - /** \brief Construct from struct tm in UTC/GMT */ - epicsTime ( const gm_tm_nano_sec & ); - /** \brief Assign from struct tm in UTC */ - epicsTime & operator = ( const gm_tm_nano_sec & ); - /** @} */ - - /** \name POSIX RT struct timespec conversions - * Convert to and from the POSIX RealTime struct timespec - * format. - * @{ */ - /** \brief Convert to struct timespec */ - operator struct timespec () const; - /** \brief Construct from struct timespec */ - epicsTime ( const struct timespec & ); - /** \brief Assign from struct timespec */ - epicsTime & operator = ( const struct timespec & ); - /** @} */ - - /** \name BSD's struct timeval conversions - * Convert to and from the BSD struct timeval format. - * @{ */ - /** \brief Convert to struct timeval */ - operator struct timeval () const; - /** \brief Construct from struct timeval */ - epicsTime ( const struct timeval & ); - /** \brief Assign from struct timeval */ - epicsTime & operator = ( const struct timeval & ); - /** @} */ - - /** \name NTP timestamp conversions - * Convert to and from the NTP timestamp structure \c l_fp - * @{ */ - /** \brief Convert to NTP format */ - operator l_fp () const; - /** \brief Construct from NTP format */ - epicsTime ( const l_fp & ); - /** \brief Assign from NTP format */ - epicsTime & operator = ( const l_fp & ); - /** @} */ - - /** \name WIN32 FILETIME conversions - * Convert to and from WIN32s _FILETIME - * \note These are only implemented on Windows targets. - * @{ */ - /** \brief Convert to Windows struct _FILETIME */ - operator struct _FILETIME () const; - /** \brief Constuct from Windows struct _FILETIME */ - epicsTime ( const struct _FILETIME & ); - /** \brief Assign from Windows struct _FILETIME */ - epicsTime & operator = ( const struct _FILETIME & ); - /** @} */ - - /** \name Arithmetic operators - * Standard operators involving epicsTime objects and time differences - * which are always expressed as a \c double in seconds. - * @{ */ - /// \brief \p lhs minus \p rhs, in seconds - double operator- ( const epicsTime & ) const; - /// \brief \p lhs plus rhs seconds - epicsTime operator+ ( const double & ) const; - /// \brief \p lhs minus rhs seconds - epicsTime operator- ( const double & ) const; - /// \brief add rhs seconds to \p lhs - epicsTime operator+= ( const double & ); - /// \brief subtract rhs seconds from \p lhs - epicsTime operator-= ( const double & ); - /** @} */ - - /** \name Comparison operators - * Standard comparisons between epicsTime objects. - * @{ */ - /// \brief \p lhs equals \p rhs - bool operator == ( const epicsTime & ) const; - /// \brief \p lhs not equal to \p rhs - bool operator != ( const epicsTime & ) const; - /// \brief \p rhs no later than \p lhs - bool operator <= ( const epicsTime & ) const; - /// \brief \p lhs was before \p rhs - bool operator < ( const epicsTime & ) const; - /// \brief \p rhs not before \p lhs - bool operator >= ( const epicsTime & ) const; - /// \brief \p lhs was after \p rhs - bool operator > ( const epicsTime & ) const; - /** @} */ - - /** \brief Convert to string in user-specified format - * - * This method extends the standard C library routine strftime(). - * See your OS documentation for details about the standard routine. - * The epicsTime method adds support for printing the fractional - * portion of the time. It searches the format string for the - * sequence %0nf where \a n is the desired precision, - * and uses this format to convert the fractional seconds with the - * requested precision. For example: - * \code{.cpp} - * epicsTime time = epicsTime::getCurrent(); - * char buf[30]; - * time.strftime(buf, 30, "%Y-%m-%d %H:%M:%S.%06f"); - * printf("%s\n", buf); - * \endcode - * This will print the current time in the format: - * \code - * 2001-01-26 20:50:29.813505 - * \endcode - */ - size_t strftime ( char * pBuff, size_t bufLength, const char * pFormat ) const; - - /** \brief Dump current state to standard out */ - void show ( unsigned interestLevel ) const; - -private: - /* - * private because: - * a) application does not break when EPICS epoch is changed - * b) no assumptions about internal storage or internal precision - * in the application - * c) it would be easy to forget which argument is nanoseconds - * and which argument is seconds (no help from compiler) - */ - epicsTime ( const unsigned long secPastEpoch, const unsigned long nSec ); - void addNanoSec ( long nanoSecAdjust ); - - unsigned long secPastEpoch; /* seconds since O000 Jan 1, 1990 */ - unsigned long nSec; /* nanoseconds within second */ -}; - -extern "C" { -#endif /* __cplusplus */ - /** \name Return status values * epicsTime routines return \c S_time_ error status values: * @{ @@ -457,6 +194,12 @@ LIBCOM_API void epicsStdCall epicsTimeAddSeconds ( epicsTimeStamp * pDest, double secondsToAdd ); /* adds seconds to *pDest */ /** @} */ +/** \brief Return difference LHS-RHS as signed integer nanoseconds. + * @since UNRELEASED + */ +LIBCOM_API +epicsInt64 epicsStdCall epicsTimeDiffInNS (const epicsTimeStamp *pLeft, const epicsTimeStamp *pRight); + /** \name Comparison operators * Comparisons between epicsTimeStamp objects, returning 0=false, 1=true. * @{ */ @@ -515,98 +258,330 @@ LIBCOM_API void osdMonotonicInit(void); #endif #ifdef __cplusplus -} +} // extern "C" + +/** \brief C++ only ANSI C struct tm with nanoseconds, local timezone + * + * Extend ANSI C "struct tm" to include nano seconds within a second + * and a struct tm that is adjusted for the local timezone. + */ +struct local_tm_nano_sec { + struct tm ansi_tm; /**< \brief ANSI C time details */ + unsigned long nSec; /**< \brief nanoseconds extension */ +}; + +/** \brief C++ only ANSI C sruct tm with nanoseconds, UTC + * + * Extend ANSI C "struct tm" to include nanoseconds within a second + * and a struct tm that is adjusted for GMT (UTC). + */ +struct gm_tm_nano_sec { + struct tm ansi_tm; /**< \brief ANSI C time details */ + unsigned long nSec; /**< \brief nanoseconds extension */ +}; + +/** \brief C++ only ANSI C time_t + * + * This is for converting to/from the ANSI C \c time_t. Since \c time_t + * is usually an elementary type providing a conversion operator from + * \c time_t to/from epicsTime could cause undesirable implicit + * conversions. Providing a conversion operator to/from the + * \c time_t_wrapper instead prevents implicit conversions. + */ +struct time_t_wrapper { + time_t ts; +}; + +/** \brief C++ Event number wrapper class + * + * Stores an event number for use by the epicsTime::getEvent() static + * class method. + */ +class LIBCOM_API epicsTimeEvent +{ +public: + epicsTimeEvent (const int &number) :eventNumber(number) {} + operator int () const { return eventNumber; } +private: + int eventNumber; +}; + +/** \brief C++ time stamp object + * + * Holds an EPICS time stamp, and provides conversion functions for both + * input and output from/to other types. + * + * \note Time conversions: The epicsTime implementation will properly + * convert between the various formats from the beginning of the EPICS + * epoch until at least 2038. Unless the underlying architecture support + * has defective POSIX, BSD/SRV5, or standard C time support the EPICS + * implementation should be valid until 2106. + */ +class LIBCOM_API epicsTime +{ + // translate S_time_* code to exception + static void throwError(int code); +public: + /// \brief Exception: Time provider problem + typedef std::runtime_error unableToFetchCurrentTime; + /// \brief Exception: Bad field(s) in struct tm + typedef std::logic_error formatProblemWithStructTM; + + /** \brief The default constructor sets the time to the EPICS epoch. */ +#if __cplusplus>=201103L + constexpr epicsTime() :ts{} {} +#else + epicsTime () { + ts.secPastEpoch = ts.nsec = 0u; + } +#endif + + /** \brief Get time of event system event. + * + * Returns an epicsTime indicating when the associated event system + * event last occurred. + */ + static inline epicsTime getEvent ( const epicsTimeEvent & evt) ; + /** \brief Get current clock time + * + * Returns an epicsTime containing the current time. For example: + * \code{.cpp} + * epicsTime now = epicsTime::getCurrent(); + * \endcode + */ + static epicsTime getCurrent (); + /** \brief Get current monotonic time + * + * Returns an epicsTime containing the current monotonic time, an + * OS clock which never going backwards or jumping forwards. + * This time is has an undefined epoch, and is only useful for + * measuring time differences. + */ + static epicsTime getMonotonic () { + epicsTime ret; + epicsTimeGetMonotonic(&ret.ts); // can't fail + return ret; + } + + /** \name epicsTimeStamp conversions + * Convert to and from EPICS epicsTimeStamp format + * @{ */ + /** \brief Convert to epicsTimeStamp */ + operator const epicsTimeStamp& () const { return ts; } + /** \brief Construct from epicsTimeStamp */ + epicsTime ( const epicsTimeStamp & replace ); + /** \brief Assign from epicsTimeStamp */ + epicsTime & operator = ( const epicsTimeStamp & replace) { + ts = replace; + return *this; + } + /** @} */ + + /** \name ANSI C time_t conversions + * Convert to and from ANSI C \c time_t wrapper . + * @{ */ + /** \brief Convert to ANSI C \c time_t */ + operator time_t_wrapper () const { + time_t_wrapper ret; + throwError(epicsTimeToTime_t(&ret.ts, &ts)); + return ret; + } + /** \brief Construct from ANSI C \c time_t */ + epicsTime ( const time_t_wrapper & replace ) { + throwError(epicsTimeFromTime_t(&ts, replace.ts)); + } + /** \brief Assign from ANSI C \c time_t */ + epicsTime & operator = ( const time_t_wrapper & replace) { + throwError(epicsTimeFromTime_t(&ts, replace.ts)); + return *this; + } + /** @} */ + + /** \name ANSI C struct tm local-time conversions + * Convert to and from ANSI Cs struct tm (with nano seconds), + * adjusted for the local time zone. + * @{ */ + /** \brief Convert to struct tm in local time zone */ + operator local_tm_nano_sec () const { + local_tm_nano_sec ret; + throwError(epicsTimeToTM(&ret.ansi_tm, 0, &ts)); + ret.nSec = ts.nsec; + return ret; + } + /** \brief Construct from struct tm in local time zone */ + epicsTime ( const local_tm_nano_sec & replace) { + throwError(epicsTimeFromTM(&ts, &replace.ansi_tm, replace.nSec)); + } + /** \brief Assign from struct tm in local time zone */ + epicsTime & operator = ( const local_tm_nano_sec & replace) { + throwError(epicsTimeFromTM(&ts, &replace.ansi_tm, replace.nSec)); + return *this; + } + /** @} */ + + /** \name ANSI C struct tm UTC conversions + * Convert to and from ANSI Cs struct tm (with nano seconds), + * adjusted for Greenwich Mean Time (UTC). + * @{ */ + /** \brief Convert to struct tm in UTC/GMT */ + operator gm_tm_nano_sec () const { + gm_tm_nano_sec ret; + throwError(epicsTimeToGMTM(&ret.ansi_tm, 0, &ts)); + ret.nSec = ts.nsec; + return ret; + } + /** \brief Construct from struct tm in UTC/GMT */ + epicsTime ( const gm_tm_nano_sec & replace) { + throwError(epicsTimeFromGMTM(&ts, &replace.ansi_tm, replace.nSec)); + } + /** \brief Assign from struct tm in UTC */ + epicsTime & operator = ( const gm_tm_nano_sec & replace) { + throwError(epicsTimeFromGMTM(&ts, &replace.ansi_tm, replace.nSec)); + return *this; + } + /** @} */ + + /** \name POSIX RT struct timespec conversions + * Convert to and from the POSIX RealTime struct timespec + * format. + * @{ */ + /** \brief Convert to struct timespec */ + operator struct timespec () const { + timespec ret; + epicsTimeToTimespec(&ret, &ts); + return ret; + } + /** \brief Construct from struct timespec */ + epicsTime ( const struct timespec & replace) { + throwError(epicsTimeFromTimespec(&ts, &replace)); + } + /** \brief Assign from struct timespec */ + epicsTime & operator = ( const struct timespec & replace ) { + throwError(epicsTimeFromTimespec(&ts, &replace)); + return *this; + } + /** @} */ + + /** \name BSD's struct timeval conversions + * Convert to and from the BSD struct timeval format. + * @{ */ + /** \brief Convert to struct timeval */ + operator struct timeval () const ; + /** \brief Construct from struct timeval */ + epicsTime ( const struct timeval & replace); + /** \brief Assign from struct timeval */ + epicsTime & operator = ( const struct timeval & replace); + /** @} */ + +#ifdef _WIN32 + /** \name WIN32 FILETIME conversions + * Convert to and from WIN32s _FILETIME + * \note These are only implemented on Windows targets. + * @{ */ + /** \brief Convert to Windows struct _FILETIME */ + operator struct _FILETIME () const; + /** \brief Constuct from Windows struct _FILETIME */ + epicsTime ( const struct _FILETIME & ); + /** \brief Assign from Windows struct _FILETIME */ + epicsTime & operator = ( const struct _FILETIME & ); + /** @} */ +#endif /* _WIN32 */ + + /** \name Arithmetic operators + * Standard operators involving epicsTime objects and time differences + * which are always expressed as a \c double in seconds. + * @{ */ + /// \brief \p lhs minus \p rhs, in seconds + double operator- ( const epicsTime & other) const { + return epicsTimeDiffInSeconds(&ts, &other.ts); + } + /// \brief \p lhs plus rhs seconds + epicsTime operator+ (double delta) const { + epicsTime ret(*this); + epicsTimeAddSeconds(&ret.ts, delta); + return ret; + } + /// \brief \p lhs minus rhs seconds + epicsTime operator- (double delta ) const { + return (*this)+(-delta); + } + /// \brief add rhs seconds to \p lhs + epicsTime operator+= (double delta) { + epicsTimeAddSeconds(&ts, delta); + return *this; + } + /// \brief subtract rhs seconds from \p lhs + epicsTime operator-= ( double delta ) { + return (*this) += (-delta); + } + /** @} */ + + /** \name Comparison operators + * Standard comparisons between epicsTime objects. + * @{ */ + /// \brief \p lhs equals \p rhs + bool operator == ( const epicsTime & other) const { + return epicsTimeEqual(&ts, &other.ts); + } + /// \brief \p lhs not equal to \p rhs + bool operator != ( const epicsTime & other) const { + return epicsTimeNotEqual(&ts, &other.ts); + } + /// \brief \p rhs no later than \p lhs + bool operator <= ( const epicsTime & other) const { + return epicsTimeLessThanEqual(&ts, &other.ts); + } + /// \brief \p lhs was before \p rhs + bool operator < ( const epicsTime & other) const { + return epicsTimeLessThan(&ts, &other.ts); + } + /// \brief \p rhs not before \p lhs + bool operator >= ( const epicsTime & other) const { + return epicsTimeGreaterThanEqual(&ts, &other.ts); + } + /// \brief \p lhs was after \p rhs + bool operator > ( const epicsTime & other) const { + return epicsTimeGreaterThan(&ts, &other.ts); + } + /** @} */ + + /** \brief Convert to string in user-specified format + * + * This method extends the standard C library routine strftime(). + * See your OS documentation for details about the standard routine. + * The epicsTime method adds support for printing the fractional + * portion of the time. It searches the format string for the + * sequence %0nf where \a n is the desired precision, + * and uses this format to convert the fractional seconds with the + * requested precision. For example: + * \code{.cpp} + * epicsTime time = epicsTime::getCurrent(); + * char buf[30]; + * time.strftime(buf, 30, "%Y-%m-%d %H:%M:%S.%06f"); + * printf("%s\n", buf); + * \endcode + * This will print the current time in the format: + * \code + * 2001-01-26 20:50:29.813505 + * \endcode + */ + size_t strftime ( char * pBuff, size_t bufLength, const char * pFormat ) const { + return epicsTimeToStrftime(pBuff, bufLength, pFormat, &ts); + } + + /** \brief Dump current state to standard out */ + void show ( unsigned interestLevel ) const { + epicsTimeShow(&ts, interestLevel); + } + +private: + epicsTimeStamp ts; +}; + +LIBCOM_API +std::ostream& operator<<(std::ostream& strm, const epicsTime& ts); + #endif /* __cplusplus */ -/* inline member functions ,*/ -#ifdef __cplusplus - -/* epicsTimeEvent */ - -inline epicsTimeEvent::epicsTimeEvent (const int &number) : - eventNumber(number) {} - -inline epicsTimeEvent::operator int () const -{ - return this->eventNumber; -} - - -/* epicsTime */ - -inline epicsTime epicsTime::operator - ( const double & rhs ) const -{ - return epicsTime::operator + ( -rhs ); -} - -inline epicsTime epicsTime::operator += ( const double & rhs ) -{ - *this = epicsTime::operator + ( rhs ); - return *this; -} - -inline epicsTime epicsTime::operator -= ( const double & rhs ) -{ - *this = epicsTime::operator + ( -rhs ); - return *this; -} - -inline bool epicsTime::operator == ( const epicsTime & rhs ) const -{ - if ( this->secPastEpoch == rhs.secPastEpoch && this->nSec == rhs.nSec ) { - return true; - } - return false; -} - -inline bool epicsTime::operator != ( const epicsTime & rhs ) const -{ - return !epicsTime::operator == ( rhs ); -} - -inline bool epicsTime::operator >= ( const epicsTime & rhs ) const -{ - return ! ( *this < rhs ); -} - -inline bool epicsTime::operator > ( const epicsTime & rhs ) const -{ - return ! ( *this <= rhs ); -} - -inline epicsTime & epicsTime::operator = ( const local_tm_nano_sec & rhs ) -{ - return *this = epicsTime ( rhs ); -} - -inline epicsTime & epicsTime::operator = ( const gm_tm_nano_sec & rhs ) -{ - return *this = epicsTime ( rhs ); -} - -inline epicsTime & epicsTime::operator = ( const struct timespec & rhs ) -{ - *this = epicsTime ( rhs ); - return *this; -} - -inline epicsTime & epicsTime::operator = ( const epicsTimeStamp & rhs ) -{ - *this = epicsTime ( rhs ); - return *this; -} - -inline epicsTime & epicsTime::operator = ( const l_fp & rhs ) -{ - *this = epicsTime ( rhs ); - return *this; -} - -inline epicsTime & epicsTime::operator = ( const time_t_wrapper & rhs ) -{ - *this = epicsTime ( rhs ); - return *this; -} -#endif /* __cplusplus */ #endif /* epicsTimehInclude */ diff --git a/modules/libcom/src/osi/os/WIN32/osdTime.cpp b/modules/libcom/src/osi/os/WIN32/osdTime.cpp index fb2cfb6c0..e2c3efbfe 100644 --- a/modules/libcom/src/osi/os/WIN32/osdTime.cpp +++ b/modules/libcom/src/osi/os/WIN32/osdTime.cpp @@ -510,8 +510,8 @@ static unsigned __stdcall _pllThreadEntry ( void * pCurrentTimeIn ) epicsTime::operator FILETIME () const { LARGE_INTEGER ftTicks; - ftTicks.QuadPart = ( this->secPastEpoch * FILE_TIME_TICKS_PER_SEC ) + - ( this->nSec / ET_TICKS_PER_FT_TICK ); + ftTicks.QuadPart = ( this->ts.secPastEpoch * FILE_TIME_TICKS_PER_SEC ) + + ( this->ts.nsec / ET_TICKS_PER_FT_TICK ); ftTicks.QuadPart += epicsEpochInFileTime; FILETIME ts; ts.dwLowDateTime = ftTicks.LowPart; @@ -527,15 +527,15 @@ epicsTime::epicsTime ( const FILETIME & ts ) if ( lift.QuadPart > epicsEpochInFileTime ) { LONGLONG fileTimeTicksSinceEpochEPICS = lift.QuadPart - epicsEpochInFileTime; - this->secPastEpoch = static_cast < epicsUInt32 > + this->ts.secPastEpoch = static_cast < epicsUInt32 > ( fileTimeTicksSinceEpochEPICS / FILE_TIME_TICKS_PER_SEC ); - this->nSec = static_cast < epicsUInt32 > + this->ts.nsec = static_cast < epicsUInt32 > ( ( fileTimeTicksSinceEpochEPICS % FILE_TIME_TICKS_PER_SEC ) * ET_TICKS_PER_FT_TICK ); } else { - this->secPastEpoch = 0; - this->nSec = 0; + this->ts.secPastEpoch = 0; + this->ts.nsec = 0; } } diff --git a/modules/libcom/test/epicsTimeTest.cpp b/modules/libcom/test/epicsTimeTest.cpp index 3e10e6808..66d13e91d 100644 --- a/modules/libcom/test/epicsTimeTest.cpp +++ b/modules/libcom/test/epicsTimeTest.cpp @@ -10,6 +10,9 @@ /* * Authors: Jeff Hill, Marty Kraimer and Andrew Johnson */ +#include + +#include #include #include #include @@ -17,6 +20,7 @@ #include #include +#include "envDefs.h" #include "epicsTime.h" #include "epicsThread.h" #include "errlog.h" @@ -29,16 +33,45 @@ using namespace std; * routines is incorporated into epicsTimeTest () below. */ -struct l_fp { /* NTP time stamp */ - epicsUInt32 l_ui; /* sec past NTP epoch */ - epicsUInt32 l_uf; /* fractional seconds */ -}; - static const unsigned mSecPerSec = 1000u; static const unsigned uSecPerSec = 1000u * mSecPerSec; static const unsigned nSecPerSec = 1000u * uSecPerSec; static const double precisionEPICS = 1.0 / nSecPerSec; +static void testAdd(epicsUInt32 lhsSec, epicsUInt32 lhsNS, + double rhs, + epicsUInt32 expectSec, epicsUInt32 expectNS) +{ + epicsTimeStamp lhs = {lhsSec, lhsNS}; + epicsTimeStamp expect = {expectSec, expectNS}; + epicsTimeStamp actual = lhs; + + + epicsTimeAddSeconds(&actual, rhs); + testOk(epicsTimeEqual(&actual, &expect), + "testAdd(%u:%u + %.9f -> %u:%u == %u:%u)", + unsigned(lhs.secPastEpoch), unsigned(lhs.nsec), + rhs, + unsigned(actual.secPastEpoch), unsigned(actual.nsec), + unsigned(expect.secPastEpoch), unsigned(expect.nsec)); +} + +static void testDiff(epicsUInt32 lhsSec, epicsUInt32 lhsNS, + epicsUInt32 rhsSec, epicsUInt32 rhsNS, + double expect) +{ + epicsTimeStamp lhs = {lhsSec, lhsNS}; + epicsTimeStamp rhs = {rhsSec, rhsNS}; + double actual = epicsTimeDiffInSeconds(&lhs, &rhs); + double diff = actual - expect; + + testOk(fabs(diff) %.9f ~= %.9f (%g)", + unsigned(lhs.secPastEpoch), unsigned(lhs.nsec), + unsigned(rhs.secPastEpoch), unsigned(rhs.nsec), + actual, expect, diff); +} + static void crossCheck(double delay) { double mindelta = 2*epicsMonotonicResolution()*1e-9, @@ -80,12 +113,91 @@ static void testMonotonic() testDiag("Small Delta %u ns", (unsigned)(B-A)); } +static void testTMGames() +{ + testDiag("testTMGames()"); + + epicsTimeStamp now; + testOk1(!epicsTimeGetCurrent(&now)); + now.nsec = 0; // not relevant + + tm gtm, ltm; + epicsTimeToTM(<m, 0, &now); + epicsTimeToGMTM(>m, 0, &now); + + // we can't do any tests on the decomposed time without knowing the current TZ + testDiag("LTM mday=%u hour=%u min=%u sec=%u", ltm.tm_mday, ltm.tm_hour, ltm.tm_min, ltm.tm_sec); + testDiag("GTM mday=%u hour=%u min=%u sec=%u", gtm.tm_mday, gtm.tm_hour, gtm.tm_min, gtm.tm_sec); + + epicsTimeStamp gtime, ltime; + epicsTimeFromTM(<ime, <m, 0); + epicsTimeFromGMTM(>ime, >m, 0); + + testOk(now.secPastEpoch==ltime.secPastEpoch, "localtime %u == %u", + unsigned(now.secPastEpoch), unsigned(ltime.secPastEpoch)); + + testOk(now.secPastEpoch==gtime.secPastEpoch, "gmtime %u == %u", + unsigned(now.secPastEpoch), unsigned(ltime.secPastEpoch)); +} + MAIN(epicsTimeTest) { const int wasteTime = 100000; const int nTimes = 10; - testPlan(22 + nTimes * 19); + testPlan(52 + nTimes * 19); + + testDiag("$TZ = \"%s\"", getenv("TZ")); + testDiag("EPICS_TZ = \"%s\"", envGetConfigParamPtr(&EPICS_TZ)); + +#if !defined(_WIN32) && !defined(vxWorks) + { + // at least glibc doesn't initialize tzname[2] until some time.h function needs the time zone + time_t junk = 0; + (void)localtime(&junk); + testDiag("Local TZ names \"%s\", \"%s\"", tzname[0], tzname[1]); + } +#endif + + // sec:ns + double == sec:ns + testAdd(0,0, 0.0, 0,0); + testAdd(1,1, 0.0, 1,1); + testAdd(1,999999999, 0.000000001, 2,0); + testAdd(1,1, 2.000000002, 3,3); + testAdd(1,0, -1.0, 0,0); + testAdd(0,1, -0.000000001, 0,0); + testAdd(1,1, -1.000000001, 0,0); + testAdd(0xffffffff,0, -1.0, 0xfffffffe,0); + testAdd(0x7fffffff,0, 1.0, 0x80000000,0); + testAdd(0x7fffffff,999999999, 0.000000001, 0x80000000,0); + + // sec:ns - sec:ns == double + testDiff(0,0, 0,0, 0.0); + + testDiff(0,1, 0,1, 0.0); + testDiff(1,0, 1,0, 0.0); + testDiff(1,1, 1,1, 0.0); + + testDiff(2,0, 1,999999999, 0.000000001); + testDiff(1,999999999, 2,0, -0.000000001); + + testDiff(1,0, 0xffffffff,0, 2.0); + testDiff(0xffffffff,0, 1,0, -2.0); + + testDiff(1,999999999, 0xffffffff,999999999, 2.0); + testDiff(0xffffffff,999999999, 1,999999999, -2.0); + + testDiff(0,999999999, 0xffffffff,0, 1.999999999); // 0.99999.. - -1.0 + testDiff(0xffffffff,0, 0,999999999, -1.999999999); // -1.0 - 0.999.. + + testDiff(0x80000000,0, 0x7fffffff,0, 1.0); + testDiff(0x7fffffff,0, 0x80000000,0, -1.0); + + testDiff(0x80000000,0, 0x7fffffff,999999999, 0.000000001); + testDiff(0x7fffffff,999999999, 0x80000000,0, -0.000000001); + + testDiff(0x80000000,999999999, 0x7fffffff,0, 1.999999999); + testDiff(0x7fffffff,0, 0x80000000,999999999, -1.999999999); try { const epicsTimeStamp epochTS = {0, 0}; @@ -113,7 +225,7 @@ MAIN(epicsTimeTest) ts.strftime(buf, sizeof(buf), pFormat); testFail("nanosecond overflow returned \"%s\"", buf); } - catch ( ... ) { + catch ( std::exception& ) { testPass("nanosecond overflow throws"); } } @@ -178,20 +290,11 @@ MAIN(epicsTimeTest) now = epicsTime::getCurrent(); testPass("default time provider"); } - catch ( ... ) { + catch ( std::exception& ) { testFail("epicsTime::getCurrent() throws"); testAbort("Can't continue, check your time provider"); } - { - l_fp ntp = now; - epicsTime tsf = ntp; - const double diff = fabs(tsf - now); - // the difference in the precision of the two time formats - static const double precisionNTP = 1.0 / (1.0 + 0xffffffff); - testOk1(diff <= precisionEPICS + precisionNTP); - } - testDiag("Running %d loops", nTimes); const epicsTime begin = epicsTime::getCurrent(); @@ -225,6 +328,7 @@ MAIN(epicsTimeTest) "now - begin ~= diff"); testOk1(begin + 0 == begin); + std::cout<<"# begin + diff ("<<(begin + diff)<<") == now ("<