5
\$\begingroup\$

I want to be able to print timestamps with milliseconds precision and time_t doesn't provide this. I also want the code to be portable, so operating specific functions are out. ftime seems to be deprecated but there doesn't seem to be a general-purpose alternative to using ftime.

How could this be improved?

#include <stdio.h>
#include <string.h>
#include <sys\timeb.h> 
#include <time.h>

static char* get_timestamp(char* buf) {
    struct timeb start;
    char append[100];
    if (buf) {
        ftime(&start);
        strftime(buf, 100, "%H:%M:%S", localtime(&start.time));

        /* append milliseconds */
        sprintf(append, ":%03u", start.millitm);
        strcat(buf, append);
    }
    return buf;
}

int main()
{
    char tmp[100];
    int countdown = 0;
    for (int i = 0; i < 10; i++, countdown = 0) {
        printf("%s\n", get_timestamp(tmp));
        while (countdown++ < 1000000)
            ;
    }
}
\$\endgroup\$
6
\$\begingroup\$

You state that operating specific functions are out, which I assume means that you don't want to use operating system specific system functions. However in this particular case that is actually hindering you from actually solving your task, as POSIX-based systems and Windows-based systems has two different ways of handling this.

Although the ftime is present in various operating system, it has been marked as obsolete as of POSIX.1-2008:

4.2BSD, POSIX.1-2001. POSIX.1-2008 removes the specification of ftime().

This function is obsolete. Don't use it. If the time in seconds suffices, time(2) can be used; gettimeofday(2) gives microseconds; clock_gettime(2) gives nanoseconds but is not as widely available.

Further is the gettimeofday() also obsolete as of POSIX.1-2008, leaving only the clock_gettime() alternative, which is not as widely available. Most notably not present in Windows, as indicated in "Porting clock_gettime to windows".

On Windows there are some other options, but seems that most fall back to using QueryPerformanceCounter & co (referred to as QPCs) as indicated in "Acquiring high-resolution time stamps". Some of my search findings also indicate that the QPCs are not be trusted in SMP (Symmetric MultiProcessing environments).

In other words, if you want to do this correctly for use in various operating systems, you do need to make it operating system dependent. If you choose to carry on with your solution using the obsolete ftime() more could surely be said, but here are some comments on your actual code:

  • Loose the append variable – You already have room in buf for the result, and know how much you've already used. I.e. replace it with something like either sprintf(&buf[8], ":%03u", start.millitm); or sprintf(buf + 8, ":%03u", start.millitm);1, and you can return without doing the strcat at the end.
  • Avoid busy loops – Your while loop is called a busy loop as it does nothing excepting counting. It is usually better to trigger a sleep function or similar, allowing the processor to do other work in between.
  • Strange reset of countdown – Instead of resetting the countdown in the for loop, it would be clearer code to reset it just before the while loop, or even better (if persisting to use the busy loop) to make it into a locally scoped for loop, i.e for (int countdown = 0; countdown < 1000000; countdown++) ;
  • Decide on one style of bracing – Decide whether you want to have opening braces on the start of the line, or end of preceding line, and stick to it. Mixing like you've done here make your code somewhat harder to read, and when mixing with empty block (aka the busy loop) with missing braces, it is not good.

    Some guys advocate a different opening brace style for functions versus other blocks, but I would suggest using the same style for everything making it more consistent.

Addendum: C11 and xtime_get or rather timespec_get

Jerry Coffin refers to a xtime_get() with potential nanoseconds precision (albeit still dependent on operating system precision). This is part of the C++ boost library. It was proposed to be included in C11 (see N1548.pdf, 7.25.7, p 383. This was revised in N1570, 7.27.2.7, into a timespec_get() function. N1570 is the latest draft available, before the standard was defined (and made commercially available). From this I quote:

3 If base is TIME_UTC, the tv_sec member is set to the number of seconds since an implementation defined epoch, truncated to a whole value and the tv_nsec member is set to the integral number of nanoseconds, rounded to the resolution of the system clock.321)
...
321) Although a struct timespec object describes times with nanosecond resolution, the available resolution is system dependent and may even be greater than 1 second.

This does however seem to have made it into C11 (also known as ISO/IEC 9899:2011), according to the table of contents listed here, see section 7.27.2.5. So if your compiler and libraries is compliant to C112, you might use timespec_get() and possibly get a system dependent precision up to nanoseconds (or even greater than 1 second).


1 Thanks to 200_success for pointing out error in code related to buf[8] not being an acceptable target for sprintf()
2 For the gcc family this seems to be gcc ver 4.6 and glibc ver 2.16, or newer

\$\endgroup\$
1
\$\begingroup\$

I answered a similar question on Stack Overflow. I wanted a timestamp in a specific format, that could be obtained from a function repeatedly.

My implementation is different than the original posters, for very specific reasons. The goal is thus to create a simple timestamp structure and a function that can create it repeatedly without arguments. I think the function below does that.

A timestamp function should return a timestamp. It should not require the user to have know the necessary length of the char*. Such details should be hidden from the user. If not the user must manage the char* or keep tabs on how users of this function pass char* variables to it.

The function clock_gettime is the ideal POSIX function, but it is not universally supported. Avoid the other deprecated functions such as gettimeofday. As someone said earlier, ftime is not the function to use, and neither is gettimeofday.

I think the code below is mostly portable. I think the trick with timestamp functions is not so much portability, but avoiding the unnecessary usage of dynamically allocated memory. I think this is important because for embedded environments several rules preclude the usage of such C constructs. Furthermore calling malloc and new are expensive operations, not good when looking for millisecond precision. My implementation could be tightened up considerably by removing the calls to the (sprintf) format functions and replacing them with something custom, but the general idea is there.

Some caveats: (1) Make sure you set the time on your embedded board. (2) Make sure your are aware of issues with your time source such as drift, GPS clock roll-over which occurs in 2019, leap second updates, time standard translations (i.e. Atomic to UTC, GPS/1980 to Unix/1970, etc).

Also, strcat requires an implicit search for '\0'. For the original solution strcat should cause no problems, but I generally avoid the use of strcat because of it's own problems.

My function returns a timestamp with a timestring (not ambiguous) and not a char* (ambiguous) - essentially this timestamp function is at a suitable code 'level', the reader knows what he is being presented with. I am not inter-mixing low and medium level code. I have a medium level type (timestamp) and a medium level function (getTimestamp) and it requires no arguments.

It would be 'nice' if clock_gettime had macro options for something like RAW_GPS_TIME, J2000_TAI_TIME, J2000_UTC_TIME and so forth, providing that such time sources were available, but this is another discussion.

struct Timestamp
{
    time_t seconds;
    long milliseconds;
    char timestring[32];
};


struct Timestamp getTimestamp()
{
    char   timebuffer[32]     = {0};
    struct timeval  tv        = {0};
    struct tm      *tmval     = NULL;
    struct tm       gmtval    = {0};
    struct timespec curtime   = {0};

    struct Timestamp timestamp;

    int i = 0;

    // Get current time
    clock_gettime(CLOCK_REALTIME, &curtime);


    // Set the fields
    timestamp.seconds      = curtime.tv_sec;
    timestamp.milliseconds = round(curtime.tv_nsec/1.0e6);

    if((tmval = gmtime_r(&timestamp.seconds, &gmtval)) != NULL)
    {
        // Build the first part of the time
        strftime(timebuffer, sizeof timebuffer, "%Y-%m-%d %H:%M:%S", &gmtval);

        // Add the milliseconds part and build the time string
        snprintf(timestamp.timestring, sizeof timestamp.timestring, "%s.%03ld", timebuffer, timestamp.milliseconds); 
    }

    return timestamp;
}

int main()
{
    int i = 0;
    struct timespec sleeptime = {0, 5000000L};

    struct Timestamp timestamp;

    for (i=0; i < 20; i++)
    {
        timestamp = getTimestamp();
        printf("Time is: %s \n", timestamp.timestring);
        nanosleep(&sleeptime, NULL);
    }

    return 0;
}

struct Timestamp getTimestamp()
{
    char   timebuffer[32]     = {0};
    struct timeval  tv        = {0};
    struct tm      *tmval     = NULL;
    struct tm       gmtval    = {0};
    struct timespec curtime   = {0};

    struct Timestamp timestamp;

    int i = 0;

    // Get current time
    clock_gettime(CLOCK_REALTIME, &curtime);


    // Set the fields
    timestamp.seconds      = curtime.tv_sec;
    timestamp.milliseconds = round(curtime.tv_nsec/1.0e6);

    if((tmval = gmtime_r(&timestamp.seconds, &gmtval)) != NULL)
    {
        // Build the first part of the time
        strftime(timebuffer, sizeof timebuffer, "%Y-%m-%d %H:%M:%S", &gmtval);

        // Add the milliseconds part and build the time string
        snprintf(timestamp.timestring, sizeof timestamp.timestring, "%s.%03ld", timebuffer, timestamp.milliseconds); 
    }

    return timestamp;
}

int main()
{
    int i = 0;
    struct timespec sleeptime = {0, 5000000L};

    struct Timestamp timestamp;

    for (i=0; i < 20; i++)
    {
        timestamp = getTimestamp();
        printf("Time is: %s \n", timestamp.timestring);
        nanosleep(&sleeptime, NULL);
    }

    return 0;
}
\$\endgroup\$
  • 1
    \$\begingroup\$ I used the following headers to compile this code: #include <time.h> #include <stdio.h> #include <math.h> #include <stdlib.h> and don't forget to add the -lm in your gcc command \$\endgroup\$ – Vladimir S. Jun 17 at 1:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.