c++ Function to format time_t as std::string: buffer length?
Asked Answered
D

5

7

I want a function that will take a time_t parameter and an arbitrary format string and format it. I want something like this:

std::string GetTimeAsString(std::string formatString, time_t theTime)
{
    struct tm *timeinfo;
    timeinfo = localtime( &theTime);

    char buffer[100];
    strftime(buffer, 100, formatString.c_str(), timeinfo);
    std::string result(buffer);
    return result;
}

However one problem I'm running into is the buffer length. I was thinking of doing something like formatString * 4 as the buffer length. But I guess you can't dynamically set the buffer length? Maybe I could pick an arbitrarily large buffer? I'm a little stuck as to how to make it generic.

How can I write a function to achieve this?

Durwyn answered 28/10, 2011 at 22:22 Comment(0)
V
7

If you have C++11:

std::string GetTimeAsString(std::string formatString, time_t theTime)
{
    struct tm *timeinfo;
    timeinfo = localtime( &theTime);

    formatString += '\a'; //force at least one character in the result
    std::string buffer;
    buffer.resize(formatstring.size());
    int len = strftime(&buffer[0], buffer.size(), formatString.c_str(), timeinfo);
    while (len == 0) {
        buffer.resize(buffer.size()*2);
        len = strftime(&buffer[0], buffer.size(), formatString.c_str(), timeinfo);
    } 
    buffer.resize(len-1); //remove that trailing '\a'
    return buffer;
}

Note I take formatString as a const reference, (for speed and safety), and use the result string as the buffer, which is faster than doing an extra copy later. I also start at the same size as the formatstring, and double the size with each attempt, but that's easily changable to something more appropriate for the results of strftime.

Varve answered 28/10, 2011 at 22:44 Comment(7)
Is that actually legal? I only ask because I recently had to investigate oper[] and it uses cplusplus.com/reference/string/string/data - this states that "the returned array points to an internal location which should not be modified directly in the program".Sundial
@Paxdiablo: For data that is correct. However, in C++11: § 21.4.1/5 For any basic_string object s, the identity &*(s.begin() + n) == &*s.begin() + n shall hold for all values of n such that 0 <= n < s.size(). and § 21.4.5/2 reference operator[](size_type pos) noexcept; ... Returns: *(begin() + pos) if pos < size() Technically what I did is legal for &buffer[0], but not buffer.c_str() or buffer.data()Varve
This will work correctly as long as the formatted string actually contains characters. Unfortunately, some format parameters (eg. %p) might yield an empty string (depending on the locale) which is indistinguishable from an error, in which case you'll get an infinite loop. The only workaround I found so far is to add an arbitrary character at the end of the format string (eg. a space) which allows to distinguish an empty result from an error, and remove that stray character before returning the string.Pluto
Also note that strftime returns the length of the formatted string excluding the trailing null character so your last resize(len-1) is incorrect here, it should just be resize(len) (unless you apply the hack I mentioned in my previous comment, in which case it does make sense to remove one more character).Pluto
@syam: cplusplus and cppreference disagree on the return type. Since cppreference has been historically more accurate, I'm going to believe you. Fixed. (Technically it wouldn't be an infinite loop, it would throw std::bad_alloc, but your point stands)Varve
@MooingDuck Well I didn't check either site, to be honest I got it from man 3 strftime which is a pretty safe bet too. ;) As to the infinite loop, my bad, wrong wording indeed.Pluto
@Pluto I know this was a while ago, but at least on gcc 6.2.0, with -std=c++11, I get an extra 0x07 character with resize(len) and it goes away with resize(len-1)Tiberias
F
5

C++11 solution with std::put_time():

std::string GetTimeAsString(std::string formatString, time_t theTime)
{
    const struct tm* timeinfo = localtime(&theTime);

    std::ostringstream os;
    os << std::put_time(timeinfo, formatString.c_str());
    return os.str();
}
Fauteuil answered 8/11, 2018 at 15:25 Comment(0)
D
3

Use a vector<char> for the buffer instead of an array. Repeatedly increase the size until strftime returns non-zero.

Dowling answered 28/10, 2011 at 22:32 Comment(3)
Or, since he's returning a string, use the string as your buffer.Varve
Only if you have C++11, @Mooing.Dowling
True, it's only valid in C++11. Otherwise vector<char> is the better bet.Varve
S
2

I would think your best bet would be to provide a fixed buffer that is likely to handle the vast majority of cases, and then do special handling for the rest. Something like (untested, except in the wetware inside my skull):

std::string GetTimeAsString (std::string formatString, time_t theTime) {
    struct tm *timeinfo;
    char buffer[100], *pBuff = buffer;
    int rc, buffSize = 100;

    timeinfo = localtime (&theTime);
    rc = strftime(pBuff, 100, formatString.c_str(), timeinfo);

    // Most times, we shouldn't enter this loop.

    while (rc == 0) {
        // Free previous in it was allocated.

        if (pBuff != buffer)
            delete[] pBuff;

        // Try with larger buffer.

        buffSize += 100;
        pBuff = new char [buffSize];
        rc = strftime(pBuff, buffSize, formatString.c_str(), timeinfo);
    }

    // Make string then free buffer if it was allocated.

    std::string result(pBuff);
    if (pBuff != buffer)
        delete[] pBuff;

    return result;
}

strftime will return zero if the provided buffer wasn't big enough. In that case, you start allocating bigger buffers until it fits.

Your non-allocated buffer size and the increment you use for allocation size can be tuned to your needs. This method has the advantage that you won't notice an efficiency hit (however small it may be) except for the rare cases - no allocation is done for that vast majority.

In addition, you could choose some other method (e.g., +10%, doubling, etc) for increasing the buffer size.

Sundial answered 28/10, 2011 at 22:34 Comment(1)
Or, since he's returning a string, use the string as your buffer. There's no speed penalty, and it's smaller code.Varve
R
0

The strftime() function returns 0 if the buffer's size is too small to hold the expected result. Using this property, you could allocate the buffer on the heap and try the consecutive powers of 2 as its size: 1, 2, 4, 8, 16 etc. until the buffer is big enough. The advantage of using the powers of 2 is that the solution's complexity is logarithmically proportional to the result's length.

There's also a special case that needs to be thought of: the format might be such that the result's size will always be 0 (e.g. an empty format). Not sure how to handle that.

Raspy answered 28/10, 2011 at 22:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.