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
    Commented May 24, 2012 at 0:49

4 Answers 4

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
    Commented 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
    Commented Jan 22, 2019 at 21:51
  • \$\begingroup\$ @michaelmoo Perhaps it should be "%s:%03d" :) \$\endgroup\$
    – Cnly
    Commented Jan 8, 2020 at 17:40
3
\$\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 <ctime>
#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. We just have to convert the time to millisecond precision, then format it:

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

int main()
{
    auto timepoint = std::chrono::system_clock::now();
    auto buffer = std::format("{0}", std::chrono::time_point_cast<std::chrono::milliseconds>(timepoint));

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

In fact, since that's the default formatter, we can simply stream the milliseconds-precision time point without explicitly using the format library:

#include <chrono>
#include <iostream>

int main()
{
    auto timepoint = std::chrono::system_clock::now();
    std::cout << std::chrono::time_point_cast<std::chrono::milliseconds>(timepoint)
              << '\n';
}
\$\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\$
0
0
\$\begingroup\$

If you don’t have C++20 then check whether you have libfmt available on your system. Its fmt::format function formats std::chrono objects in much the same way, and the %S format specifier will automatically output fractional seconds if the time unit of the duration parameter is sub-second.

\$\endgroup\$
4
  • 1
    \$\begingroup\$ Welcome to Code Review! The question specifically says that non-Standard libraries are not desired. Also, note that %S conversion may produce a different length fraction to the desired precision, and the formatter doesn't allow a precision specifier. \$\endgroup\$ Commented Jan 13 at 10:01
  • \$\begingroup\$ Depends what you means by “standard”. Libfmt is part of the Debian distro so it’s pretty standard there. As for precision, the docs say that you get the precision of the underlying data type, so if you have a chrono::milliseconds type then you get milliseconds. \$\endgroup\$
    – kbro
    Commented Jan 14 at 15:43
  • \$\begingroup\$ I think most people would agree that libfmt is a "third-party library like Boost." Boost is also available in Debian, and if that's not welcome, we can presume libfmt isn't either. \$\endgroup\$ Commented Jan 14 at 18:17
  • \$\begingroup\$ Given that the question was asked 11 years 8 months ago when C++11 was the state of the art, and a "desired" solution didn't arrive until C++20, it would seem likely that the OP's desires were not met and they had to compromise. I posted my answer to help others today (2024) who for whatever reason cannot use C++20 (my embedded board it stuck on Debian Bullseye with GCC-10) but want something lighter than Boost, in which case libfmt might meet their needs. But, sure, let's argue about what the OP asked for over a decade ago, because that's a great use of our time. \$\endgroup\$
    – kbro
    Commented Jan 17 at 1:48

Your Answer

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

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