Skip to content

Commit

Permalink
fix nasa#2316 - adding CFE_TIME_StringFmt() and CFE_TIME_StringFmtLen…
Browse files Browse the repository at this point in the history
…() functions
  • Loading branch information
CDKnightNASA committed May 24, 2023
1 parent b429d91 commit 596e521
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 58 deletions.
81 changes: 67 additions & 14 deletions modules/core_api/fsw/inc/cfe_time.h
Original file line number Diff line number Diff line change
Expand Up @@ -675,37 +675,90 @@ CFE_Status_t CFE_TIME_UnregisterSynchCallback(CFE_TIME_SynchCallbackPtr_t Callba

/*****************************************************************************/
/**
** \brief Print a time value as a string
** \brief Compute the expected string length for a given time format.
**
** \par Description
** This routine prints the specified time to the specified string buffer
** in the following format: <br> <br>
** \c yyyy-ddd-hh:mm:ss.xxxxx\\0 <br> <br>
** where:
** - \c yyyy = year
** - \c ddd = Julian day of the year
** - \c hh = hour of the day (0 to 23)
** - \c mm = minute (0 to 59)
** - \c ss = second (0 to 59)
** - \c xxxxx = subsecond formatted as a decimal fraction (1/4 second = 0.25000)
** - \c \\0 = trailing null
** This computes the number of characters it will take to
** represent a time value as the string defined by its format.
**
** \param[in] TimeFormat The format string defining what is expected.
**
** \param[out] FmtLen The length computed.
**
** \return Execution status, see \ref CFEReturnCodes
** \retval #CFE_SUCCESS \copybrief CFE_SUCCESS
** \retval #CFE_TIME_BAD_ARGUMENT \copybrief CFE_TIME_BAD_ARGUMENT
**
** \sa #CFE_TIME_StringFmt, #CFE_TIME_Print
**
******************************************************************************/
CFE_Status_t CFE_TIME_StringFmtLen(const char *TimeFormat, size_t *FmtLen);

/*****************************************************************************/
/**
** \brief Produce a formatted string representation of a time value.
**
** \par Description
** This routine formats the specified time to the specified string buffer
** as defined by a format string, wherein the following format
** specifiers are replaced, and the string is null-terminated.
** Where:
** - \c %Y -> yyyy = year
** - \c %j -> ddd = Julian day of the year
** - \c %H -> hh = hour of the day (0 to 23)
** - \c %M -> mm = minute (0 to 59)
** - \c %S -> ss = second (0 to 59)
** - \c %f -> xxxxx = subsecond formatted as a decimal fraction (1/4 second = 0.25000)
** - \c %% -> %
** - \c <any other char> -> <same>
**
** \par Assumptions, External Events, and Notes:
** - The value of the time argument is simply added to the configuration
** definitions for the ground epoch and converted into a fixed length
** string in the buffer provided by the caller.
**
** - A loss of data during the string conversion will occur if the
** computed year exceeds 9999. However, a year that large would
** require an unrealistic definition for the ground epoch since
** the maximum amount of time represented by a CFE_TIME_SysTime
** structure is approximately 136 years.
**
** \param[out] StringBuffer Pointer to a character array @nonnull of
** the length as defined by the below StringBufferSize
** parameter.
**
** \param[in] StringBufferSize The size of the memory block pointed to by StringBuffer.
**
** \param[in] TimeFormat The format string defining what is expected in the output
** as described above.
**
** \param[in] TimeToFormat The time to format into the character array.
**
** \return Execution status, see \ref CFEReturnCodes
** \retval #CFE_SUCCESS \copybrief CFE_SUCCESS
** \retval #CFE_TIME_BAD_ARGUMENT \copybrief CFE_TIME_BAD_ARGUMENT
**
** \sa #CFE_TIME_StringFmtLen, #CFE_TIME_Print
**
******************************************************************************/
CFE_Status_t CFE_TIME_StringFmt(char *StringBuffer, size_t StringBufferSize, const char *TimeFormat, CFE_TIME_SysTime_t TimeToFmt);

/*****************************************************************************/
/**
** \brief Produce a formatted string representation of a time value
**
** \par Description
** This routine prints the specified time to the specified string buffer
** in the format "%Y-%d-%H:%M:%S.%f" (aka yyyy-ddd-hh:mm:ss.xxxxx).
** The output string will be null-terminated.
**
** \param[out] PrintBuffer Pointer to a character array @nonnull of at least
** #CFE_TIME_PRINTED_STRING_SIZE characters in length. *PrintBuffer is the time as a
** character string as described above.
** #CFE_TIME_PRINTED_STRING_SIZE characters in length.
**
** \param[in] TimeToPrint The time to print into the character array.
**
** \sa #CFE_TIME_StringFmtLen, #CFE_TIME_StringFmt
**
******************************************************************************/
void CFE_TIME_Print(char *PrintBuffer, CFE_TIME_SysTime_t TimeToPrint);

Expand Down
190 changes: 146 additions & 44 deletions modules/time/fsw/src/cfe_time_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -563,8 +563,66 @@ uint32 CFE_TIME_Micro2SubSecs(uint32 MicroSeconds)
* See description in header file for argument/return detail
*
*-----------------------------------------------------------------*/
void CFE_TIME_Print(char *PrintBuffer, CFE_TIME_SysTime_t TimeToPrint)
CFE_Status_t CFE_TIME_StringFmtLen(const char *TimeFormat, size_t *FmtLen)
{
*FmtLen = 0;
const char *TimeFormatPtr = TimeFormat;

while (TimeFormatPtr[0] != '\0')
{
if(*TimeFormatPtr == '%')
{
TimeFormatPtr++;
switch(*TimeFormatPtr)
{
case 'f':
*FmtLen += 5;
TimeFormatPtr++;
break;
case 'Y':
*FmtLen += 4;
TimeFormatPtr++;
break;
case 'j':
*FmtLen += 3;
TimeFormatPtr++;
break;
case 'H':
case 'M':
case 'S':
*FmtLen += 2;
TimeFormatPtr++;
break;
case '%':
*FmtLen += 1;
TimeFormatPtr++;
break;
default:
/* unknown format character */
return CFE_TIME_BAD_ARGUMENT;
}
}
else
{
(*FmtLen)++;
TimeFormatPtr++;
}
}
(*FmtLen)++; /* null-terminator */

return CFE_SUCCESS;
}

/*----------------------------------------------------------------
*
* Implemented per public API
* See description in header file for argument/return detail
*
*-----------------------------------------------------------------*/
CFE_Status_t CFE_TIME_StringFmt(char *StringBuffer, size_t StringBufferSize, const char *TimeFormat, CFE_TIME_SysTime_t TimeToFormat)
{
CFE_Status_t status = CFE_SUCCESS;
size_t OutputSize = 0;
uint32 NumberOfYears;
uint32 NumberOfDays;
uint32 NumberOfHours;
Expand All @@ -575,21 +633,35 @@ void CFE_TIME_Print(char *PrintBuffer, CFE_TIME_SysTime_t TimeToPrint)

bool StillCountingYears = true;

if (PrintBuffer == NULL)
if (StringBuffer == NULL || TimeFormat == NULL)
{
CFE_ES_WriteToSysLog("%s: Failed invalid arguments\n", __func__);
return;

return CFE_TIME_BAD_ARGUMENT;
}

status = CFE_TIME_StringFmtLen(TimeFormat, &OutputSize);
if (status != CFE_SUCCESS)
{
return status;
}

if (OutputSize > StringBufferSize)
{
CFE_ES_WriteToSysLog("%s: Failed invalid arguments\n", __func__);

return CFE_TIME_BAD_ARGUMENT;
}

/*
** Convert the cFE time (offset from epoch) into calendar time...
*/
NumberOfMicros = CFE_TIME_Sub2MicroSecs(TimeToPrint.Subseconds) + CFE_MISSION_TIME_EPOCH_MICROS;
NumberOfMicros = CFE_TIME_Sub2MicroSecs(TimeToFormat.Subseconds) + CFE_MISSION_TIME_EPOCH_MICROS;

NumberOfMinutes = (NumberOfMicros / 60000000) + (TimeToPrint.Seconds / 60) + CFE_MISSION_TIME_EPOCH_MINUTE;
NumberOfMinutes = (NumberOfMicros / 60000000) + (TimeToFormat.Seconds / 60) + CFE_MISSION_TIME_EPOCH_MINUTE;
NumberOfMicros = NumberOfMicros % 60000000;

NumberOfSeconds = (NumberOfMicros / 1000000) + (TimeToPrint.Seconds % 60) + CFE_MISSION_TIME_EPOCH_SECOND;
NumberOfSeconds = (NumberOfMicros / 1000000) + (TimeToFormat.Seconds % 60) + CFE_MISSION_TIME_EPOCH_SECOND;
NumberOfMicros = NumberOfMicros % 1000000;
/*
** Adding the epoch "seconds" after computing the minutes avoids
Expand Down Expand Up @@ -669,44 +741,74 @@ void CFE_TIME_Print(char *PrintBuffer, CFE_TIME_SysTime_t TimeToPrint)
*/
NumberOfMicros = NumberOfMicros / 10;

/*
** Build formatted output string (yyyy-ddd-hh:mm:ss.xxxxx)...
*/
*PrintBuffer++ = '0' + (char)(NumberOfYears / 1000);
NumberOfYears = NumberOfYears % 1000;
*PrintBuffer++ = '0' + (char)(NumberOfYears / 100);
NumberOfYears = NumberOfYears % 100;
*PrintBuffer++ = '0' + (char)(NumberOfYears / 10);
*PrintBuffer++ = '0' + (char)(NumberOfYears % 10);
*PrintBuffer++ = '-';

*PrintBuffer++ = '0' + (char)(NumberOfDays / 100);
NumberOfDays = NumberOfDays % 100;
*PrintBuffer++ = '0' + (char)(NumberOfDays / 10);
*PrintBuffer++ = '0' + (char)(NumberOfDays % 10);
*PrintBuffer++ = '-';

*PrintBuffer++ = '0' + (char)(NumberOfHours / 10);
*PrintBuffer++ = '0' + (char)(NumberOfHours % 10);
*PrintBuffer++ = ':';

*PrintBuffer++ = '0' + (char)(NumberOfMinutes / 10);
*PrintBuffer++ = '0' + (char)(NumberOfMinutes % 10);
*PrintBuffer++ = ':';

*PrintBuffer++ = '0' + (char)(NumberOfSeconds / 10);
*PrintBuffer++ = '0' + (char)(NumberOfSeconds % 10);
*PrintBuffer++ = '.';

*PrintBuffer++ = '0' + (char)(NumberOfMicros / 10000);
NumberOfMicros = NumberOfMicros % 10000;
*PrintBuffer++ = '0' + (char)(NumberOfMicros / 1000);
NumberOfMicros = NumberOfMicros % 1000;
*PrintBuffer++ = '0' + (char)(NumberOfMicros / 100);
NumberOfMicros = NumberOfMicros % 100;
*PrintBuffer++ = '0' + (char)(NumberOfMicros / 10);
*PrintBuffer++ = '0' + (char)(NumberOfMicros % 10);
*PrintBuffer++ = '\0';
while (*TimeFormat != '\0')
{
if (*TimeFormat == '%')
{
TimeFormat++;
switch(*TimeFormat)
{
case 'Y':
/*
** Build formatted output string (yyyy-ddd-hh:mm:ss.xxxxx)...
*/
*(StringBuffer++) = '0' + (char)(NumberOfYears / 1000);
*(StringBuffer++) = '0' + (char)((NumberOfYears % 1000) / 100);
*(StringBuffer++) = '0' + (char)((NumberOfYears % 100) / 10);
*(StringBuffer++) = '0' + (char)(NumberOfYears % 10);
break;
case 'j':
*(StringBuffer++) = '0' + (char)(NumberOfDays / 100);
*(StringBuffer++) = '0' + (char)((NumberOfDays % 100) / 10);
*(StringBuffer++) = '0' + (char)(NumberOfDays % 10);
break;
case 'H':
*(StringBuffer++) = '0' + (char)(NumberOfHours / 10);
*(StringBuffer++) = '0' + (char)(NumberOfHours % 10);
break;
case 'M':
*(StringBuffer++) = '0' + (char)(NumberOfMinutes / 10);
*(StringBuffer++) = '0' + (char)(NumberOfMinutes % 10);
break;
case 'S':
*(StringBuffer++) = '0' + (char)(NumberOfSeconds / 10);
*(StringBuffer++) = '0' + (char)(NumberOfSeconds % 10);
break;
case 'f':
*(StringBuffer++) = '0' + (char)(NumberOfMicros / 10000);
*(StringBuffer++) = '0' + (char)((NumberOfMicros % 10000) / 1000);
*(StringBuffer++) = '0' + (char)((NumberOfMicros % 1000) / 100);
*(StringBuffer++) = '0' + (char)((NumberOfMicros % 100) / 10);
*(StringBuffer++) = '0' + (char)(NumberOfMicros % 10);
break;
case '%':
*(StringBuffer++) = '%';
break;
default:
/* should never get here, CFE_TIME_StringFmtLen should have caught this error */
return CFE_TIME_BAD_ARGUMENT;
}
TimeFormat++;
}
else
{
*(StringBuffer++) = *(TimeFormat++);
}
}
*StringBuffer = '\0';

return CFE_SUCCESS;
}

/*----------------------------------------------------------------
*
* Implemented per public API
* See description in header file for argument/return detail
*
*-----------------------------------------------------------------*/
void CFE_TIME_Print(char *PrintBuffer, CFE_TIME_SysTime_t TimeToPrint)
{
CFE_TIME_StringFmt(PrintBuffer, CFE_TIME_PRINTED_STRING_SIZE, "%Y-%j-%H:%M:%S.%f", TimeToPrint);
}

/*----------------------------------------------------------------
Expand Down
23 changes: 23 additions & 0 deletions modules/time/ut-coverage/time_UT.c
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,7 @@ void Test_Print(void)
char expectedBuf[CFE_TIME_PRINTED_STRING_SIZE];
CFE_TIME_SysTime_t time;
bool usingDefaultEpoch = true;
size_t timeFmtLen;

memset(&time, 0, sizeof(time));

Expand Down Expand Up @@ -893,6 +894,28 @@ void Test_Print(void)
(unsigned int)time.Seconds, (unsigned int)time.Subseconds, timeBuf);
}

/* test nominal StringFmt function */
CFE_UtAssert_SUCCESS(CFE_TIME_StringFmt(timeBuf, CFE_TIME_PRINTED_STRING_SIZE, "%Y%j%H%M%S%f", time));

if (usingDefaultEpoch)
{
strcpy(expectedBuf, "201300102030400005");
UtAssert_STRINGBUF_EQ(timeBuf, sizeof(timeBuf), expectedBuf, sizeof(expectedBuf));
}
else
{
UtAssert_MIR("Confirm adding seconds = %u, subseconds = %u to configured EPOCH results in time %s",
(unsigned int)time.Seconds, (unsigned int)time.Subseconds, timeBuf);
}

/* Test for invalid format string */
UtAssert_INT32_EQ(CFE_TIME_StringFmtLen("%a%b%c%d", &timeFmtLen), CFE_TIME_BAD_ARGUMENT);

/* Test for insufficiently-sized format output buffer */
UtAssert_INT32_EQ(
CFE_TIME_StringFmt(timeBuf, CFE_TIME_PRINTED_STRING_SIZE - 1,
"%y-%j-%H:%M:%S.%f", time), CFE_TIME_BAD_ARGUMENT);

/* Test with maximum seconds and subseconds values */
time.Subseconds = 0xffffffff;
time.Seconds = 0xffffffff;
Expand Down

0 comments on commit 596e521

Please sign in to comment.