11
\$\begingroup\$

I am looking for a more efficient or shorter way to achieve the following output using the following code:

timeval curTime;
gettimeofday(&curTime, NULL);
int milli = curTime.tv_usec / 1000;

time_t rawtime;
struct tm * timeinfo;
char buffer [80];

time(&rawtime);
timeinfo = localtime(&rawtime);

strftime(buffer, 80, "%Y-%m-%d %H:%M:%S", timeinfo);

char currentTime[84] = "";
sprintf(currentTime, "%s:%d", buffer, milli);
printf("current time: %s \n", currentTime);

Sample output:

current time: 2012-05-16 13:36:56:396

I would prefer not to use any third-party library like Boost.

\$\endgroup\$
1
  • 2
    \$\begingroup\$ You want to use %03d to format milli so you get leading zeros. And since your format is so close to ISO-8601, you might want to continue with it and use "." instead of ":" to separate the seconds and milliseconds. \$\endgroup\$
    – xan
    May 24, 2012 at 0:49

3 Answers 3

12
\$\begingroup\$

When you call gettimeofday it gives you the number of seconds since EPOCH too, so you don't need to call time again. And when you use output of localtime as input of strftime, you may omit the intermediate variable (not a very useful point though). So your code could be written like:

timeval curTime;
gettimeofday(&curTime, NULL);
int milli = curTime.tv_usec / 1000;

char buffer [80];
strftime(buffer, 80, "%Y-%m-%d %H:%M:%S", localtime(&curTime.tv_sec));

char currentTime[84] = "";
sprintf(currentTime, "%s:%03d", buffer, milli);
printf("current time: %s \n", currentTime);

an important note to be considered is that functions like localtime are not thread-safe, and you'd better use localtime_r instead.

\$\endgroup\$
3
  • \$\begingroup\$ It's worth to note that on Windows timeval::tv_sec is a long, while localtime expects a time_t. You might want explicitly convert it to the right type first. \$\endgroup\$
    – Kentzo
    Feb 28, 2018 at 7:46
  • \$\begingroup\$ sprintf(currentTime, "%s:%d", buffer, milli); will be wrong when milli<100. Use "%s:03d" to add zero-padding. \$\endgroup\$
    – michaelmoo
    Jan 22, 2019 at 21:51
  • \$\begingroup\$ @michaelmoo Perhaps it should be "%s:%03d" :) \$\endgroup\$
    – Cnly
    Jan 8, 2020 at 17:40
2
\$\begingroup\$

There's a serious problem here:

gettimeofday(&curTime, NULL);
⋮
time(&rawtime);

Suppose the system time is approximately HH:MM:00.999 when curTime is assigned, but a few microseconds later at HH:MM:01.000 when rawtime is assigned. This means that we'll print HH:MM:01.999, which is quite far from either value.

Swapping the two calls won't help - that would result in HH:MM:00.000, which is also wrong. We need to get a single precise value, and use that for the whole of the formatting.

Instead of writing out %Y-%m-%d and %H:%M:%S, it's easier and arguably clearer to use the shorter forms %F and %T. And we can use the return value from std::strftime() to determine the position at which the fractional seconds should be formatted.

We can use the size of the longest expected output to determine how large the buffer needs to be:

    char buffer[sizeof "9999-12-31 23:59:59.999"];

In modern C++, we would use std::chrono for time access, rather than the C library:

#include <chrono>
#include <iostream>

int main()
{
    using namespace std::chrono;
    auto timepoint = system_clock::now();
    auto coarse = system_clock::to_time_t(timepoint);
    auto fine = time_point_cast<std::chrono::milliseconds>(timepoint);

    char buffer[sizeof "9999-12-31 23:59:59.999"];
    std::snprintf(buffer + std::strftime(buffer, sizeof buffer - 3,
                                         "%F %T.", std::localtime(&coarse)),
                  4, "%03lu", fine.time_since_epoch().count() % 1000);

    std::cout << buffer << '\n';
}

If you have a C++ environment that implements C++20's std::format, then it becomes much, much simpler:

#include <chrono>
#include <format>
#include <iostream>

// UNTESTED!!
int main()
{
    auto timepoint = std::chrono::system_clock::now();

    auto buffer = std::format("%F %R:%06.3S", timepoint.time_since_epoch());

    std::cout << buffer << '\n';
}

I'm not yet sure whether we can supply a precision specifier with %T conversion, so I split the time representation. I may try that out when I have access to a GCC with sufficient support.

\$\endgroup\$
0
2
\$\begingroup\$

You can remove the necessity to use two buffers, and also they are too large (80? why?). The actual string will be something like 23 characters long.

If you want to be cool and expect your program to last until end of 64bit Unix timestamp, which is the year 292277026596, then we need 8 additional characters to fit. So 31 bytes for the actual characters in total.

And + 1 byte for \0 which makes it nice and round 32 bytes.

timeval curTime;
char buffer[32];

gettimeofday(&curTime, NULL);
size_t endpos = strftime(buffer, sizeof buffer,
                         "%Y-%m-%d %H:%M:%S", localtime(&curTime.tv_sec));
snprintf(buffer + endpos, sizeof buffer - endpos,
         ":%03d", (int) (curTime.tv_usec / 1000));

This code is not thread safe (use localtime_r()).

\$\endgroup\$
1
  • 1
    \$\begingroup\$ To make it portable, just cast the result of curTime.tv_usec / 1000 to int and format it with %03d. I would also use snprintf() just to be safe, and use sizeof buffer instead of repeating 32. \$\endgroup\$
    – G. Sliepen
    Jul 30, 2021 at 21:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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