Files
epics-base/modules/libcom/src/osi/epicsTime.cpp
2023-12-14 11:31:36 -06:00

481 lines
15 KiB
C++

/*************************************************************************\
* Copyright (c) 2011 LANS LLC, as Operator of
* Los Alamos National Laboratory.
* Copyright (c) 2002 The Regents of the University of California, as
* Operator of Los Alamos National Laboratory.
* Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne
* National Laboratory.
* SPDX-License-Identifier: EPICS
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
/* epicsTime.cpp */
/* Author Jeffrey O. Hill */
#include <stdexcept>
#include <ctype.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <float.h>
#include <string> // vxWorks 6.0 requires this include
#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 epicsUInt32 nSecPerSec = 1000000000u;
static const unsigned nSecFracDigits = 9u;
void epicsTime::throwError(int code)
{
if(code==epicsTimeOK)
return;
throw std::logic_error(errSymMsg(code));
}
epicsTime::epicsTime ( const epicsTimeStamp & replace )
:ts(replace)
{
if(ts.nsec >= nSecPerSec)
throw std::logic_error("epicsTimeStamp has overflow in nano-seconds field");
}
epicsTime epicsTime::getCurrent ()
{
epicsTimeStamp current;
int status = epicsTimeGetCurrent (&current);
if (status) {
throw unableToFetchCurrentTime ("Unable to fetch Current Time");
}
return epicsTime ( current );
}
epicsTime epicsTime::getEvent (const epicsTimeEvent &event)
{
epicsTimeStamp current;
int status = epicsTimeGetEvent (&current, event);
if (status) {
throw unableToFetchCurrentTime ("Unable to fetch Event Time");
}
return epicsTime ( current );
}
epicsTime::operator struct timeval () const {
timeval ret;
epicsTimeToTimeval(&ret, &ts);
return ret;
}
epicsTime::epicsTime ( const struct timeval & replace) {
throwError(epicsTimeFromTimeval(&ts, &replace));
}
epicsTime & epicsTime::operator = ( const struct timeval & replace) {
throwError(epicsTimeFromTimeval(&ts, &replace));
return *this;
}
std::ostream& operator<<(std::ostream& strm, const epicsTime& ts)
{
char temp[64];
(void)ts.strftime(temp, sizeof(temp), "%Y-%m-%d %H:%M:%S.%09f");
temp[sizeof(temp)-1u] = '\0';
return strm<<temp;
}
// Break up a format string into "<strftime prefix>%0<nnn>f<postfix>"
// (where <nnn> in an unsigned integer)
// Result:
// A) Copies a prefix which is valid for ANSI strftime into the supplied
// buffer setting the buffer to an empty string if no prefix is present.
// B) Indicates whether a valid "%0<n>f]" is present or not and if so
// specifying its nnnn
// C) returning a pointer to the postfix (which might be passed again
// to fracFormatFind.
static const char * fracFormatFind (
const char * const pFormat,
char * const pPrefixBuf,
const size_t prefixBufLen,
bool & fracFmtFound,
unsigned long & fracFmtWidth )
{
assert ( prefixBufLen > 1 );
unsigned long width = 0xffffffff;
bool fracFound = false;
const char * pAfter = pFormat;
const char * pFmt = pFormat;
while ( *pFmt != '\0' ) {
if ( *pFmt == '%' ) {
if ( pFmt[1] == '%' ) {
pFmt++;
}
else if ( pFmt[1] == 'f' ) {
fracFound = true;
pAfter = & pFmt[2];
break;
}
else {
errno = 0;
char * pAfterTmp;
unsigned long result = strtoul ( pFmt + 1, & pAfterTmp, 10 );
if ( errno == 0 && *pAfterTmp == 'f' && result > 0 ) {
width = result;
fracFound = true;
pAfter = pAfterTmp + 1;
break;
}
}
}
pFmt++;
pAfter = pFmt;
}
size_t len = pFmt - pFormat;
if ( len < prefixBufLen ) {
strncpy ( pPrefixBuf, pFormat, len );
pPrefixBuf [ len ] = '\0';
if ( fracFound ) {
fracFmtFound = true;
fracFmtWidth = width;
}
else {
fracFmtFound = false;
}
}
else {
strncpy ( pPrefixBuf, "<invalid format>", prefixBufLen - 1 );
pPrefixBuf [ prefixBufLen - 1 ] = '\0';
fracFmtFound = false;
pAfter = "";
}
return pAfter;
}
//
// size_t epicsTime::strftime ()
//
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 ( pTS->secPastEpoch == 0 && pTS->nsec == 0u ) {
strncpy ( pBuff, "<undefined>", bufLength );
pBuff[bufLength-1] = '\0';
return strlen ( pBuff );
}
char * pBufCur = pBuff;
const char * pFmt = pFormat;
size_t bufLenLeft = bufLength;
while ( *pFmt != '\0' && bufLenLeft > 1 ) {
// look for "%0<n>f" at the end (used for fractional second formatting)
char strftimePrefixBuf [256];
bool fracFmtFound;
unsigned long fracWid = 0;
pFmt = fracFormatFind (
pFmt,
strftimePrefixBuf, sizeof ( strftimePrefixBuf ),
fracFmtFound, fracWid );
// nothing more in the string, then quit
if ( ! ( strftimePrefixBuf[0] != '\0' || fracFmtFound ) ) {
break;
}
// all but fractional seconds use strftime formatting
if ( strftimePrefixBuf[0] != '\0' ) {
tm tm;
(void)epicsTimeToTM(&tm, 0, pTS);
size_t strftimeNumChar = :: strftime (
pBufCur, bufLenLeft, strftimePrefixBuf, &tm );
pBufCur [ strftimeNumChar ] = '\0';
pBufCur += strftimeNumChar;
bufLenLeft -= strftimeNumChar;
}
// fractional seconds formatting
if ( fracFmtFound && bufLenLeft > 1 ) {
if ( fracWid > nSecFracDigits ) {
fracWid = nSecFracDigits;
}
// verify that there are enough chars left for the fractional seconds
if ( fracWid < bufLenLeft )
{
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 ),
static_cast < unsigned long > ( 1e8 ),
static_cast < unsigned long > ( 1e7 ),
static_cast < unsigned long > ( 1e6 ),
static_cast < unsigned long > ( 1e5 ),
static_cast < unsigned long > ( 1e4 ),
static_cast < unsigned long > ( 1e3 ),
static_cast < unsigned long > ( 1e2 ),
static_cast < unsigned long > ( 1e1 ),
static_cast < unsigned long > ( 1e0 )
};
// round without overflowing into whole seconds
unsigned long frac = pTS->nsec + div[fracWid] / 2;
if (frac >= nSecPerSec)
frac = nSecPerSec - 1;
// convert nanosecs to integer of correct range
frac /= div[fracWid];
char fracFormat[32];
epicsSnprintf ( fracFormat, sizeof ( fracFormat ), "%%0%lulu", fracWid );
int status = epicsSnprintf ( pBufCur, bufLenLeft, fracFormat, frac );
if ( status > 0 ) {
unsigned long nChar = static_cast < unsigned long > ( status );
if ( nChar >= bufLenLeft ) {
nChar = bufLenLeft - 1;
}
pBufCur[nChar] = '\0';
pBufCur += nChar;
bufLenLeft -= nChar;
}
}
else {
static const char pOVF [] = "OVF";
size_t tmpLen = sizeof ( pOVF ) - 1;
if ( tmpLen >= bufLenLeft ) {
tmpLen = bufLenLeft - 1;
}
strncpy ( pBufCur, pOVF, tmpLen );
pBufCur[tmpLen] = '\0';
pBufCur += tmpLen;
bufLenLeft -= tmpLen;
}
}
else {
static const char pDoesntFit [] = "************";
size_t tmpLen = sizeof ( pDoesntFit ) - 1;
if ( tmpLen >= bufLenLeft ) {
tmpLen = bufLenLeft - 1;
}
strncpy ( pBufCur, pDoesntFit, tmpLen );
pBufCur[tmpLen] = '\0';
pBufCur += tmpLen;
bufLenLeft -= tmpLen;
break;
}
}
}
return pBufCur - pBuff;
}
//
// epicsTime::show (unsigned)
//
void epicsStdCall epicsTimeShow (const epicsTimeStamp *pTS, unsigned level)
{
char bigBuffer[256];
size_t numChar = epicsTimeToStrftime( bigBuffer, sizeof ( bigBuffer ),
"%a %b %d %Y %H:%M:%S.%09f", pTS );
if ( numChar > 0 ) {
printf ( "epicsTime: %s\n", bigBuffer );
}
}
int epicsStdCall epicsTimeToTime_t (time_t *pDest, const epicsTimeStamp *pSrc)
{
STATIC_ASSERT(sizeof(*pDest) >= sizeof(pSrc->secPastEpoch));
// widen to 64-bit to (eventually) accommodate 64-bit time_t
*pDest = epicsUInt64(pSrc->secPastEpoch) + POSIX_TIME_AT_EPICS_EPOCH;
return epicsTimeOK;
}
int epicsStdCall epicsTimeFromTime_t (epicsTimeStamp *pDest, time_t src)
{
pDest->secPastEpoch = epicsInt64(src) - POSIX_TIME_AT_EPICS_EPOCH;
pDest->nsec = 0;
return epicsTimeOK;
}
int epicsStdCall epicsTimeToTM (struct tm *pDest, unsigned long *pNSecDest, const epicsTimeStamp *pSrc)
{
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;
}
int epicsStdCall epicsTimeToGMTM (struct tm *pDest, unsigned long *pNSecDest, const epicsTimeStamp *pSrc)
{
time_t temp;
int err;
err = epicsTimeToTime_t(&temp, pSrc);
if(!err)
err = epicsTime_gmtime(&temp, pDest);
if(!err && pNSecDest)
*pNSecDest = pSrc->nsec;
return err;
}
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;
}