C++函数:将time_t格式化为std::string:缓冲区长度是多少?

7
我希望有一个函数,它可以接受一个time_t参数和任意格式的字符串,并进行格式化。我需要类似于以下代码的函数:
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;
}

然而,我遇到的一个问题是缓冲区长度。我正在考虑像formatString * 4这样的东西作为缓冲区长度。但是我猜你不能动态设置缓冲区长度?也许我可以选择一个任意大的缓冲区?我有点困惑如何使它通用。

我该如何编写一个函数来实现这个目标?

5个回答

6
如果您使用的是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;
}

请注意,我将formatString作为常量引用(为了速度和安全性),并使用结果字符串作为缓冲区,这比稍后进行额外复制更快。我还从与formatstring相同的大小开始,并且每次尝试都将其大小加倍,但可以轻松更改为适合strftime结果的更适当的大小。

1
@Paxdiablo:对于data是正确的。然而,在C++11中:§21.4.1/5 对于任何basic_string对象s,当0 <= n < s.size()时,恒有&*(s.begin() + n) == &*s.begin() + n,以及§21.4.5/2 reference operator[](size_type pos) noexcept; ... 如果pos < size()则返回:*(begin() + pos)。从技术上讲,我对于&buffer[0]是合法的,但不适用于buffer.c_str()buffer.data() - Mooing Duck
1
只要格式化字符串实际包含字符,这将正常工作。不幸的是,一些格式参数(例如 %p)可能会产生一个空字符串(取决于语言环境),这与错误无法区分,这种情况下你将得到一个无限循环。我目前找到的唯一解决方法是在格式字符串的末尾添加任意字符(例如空格),这允许区分空结果和错误,并在返回字符串之前删除该杂项字符。 - syam
请注意,strftime返回格式化字符串的长度,不包括尾随的空字符,因此您最后的resize(len-1)在这里是不正确的,它应该只是resize(len)(除非您应用了我在先前评论中提到的hack,在这种情况下,删除一个字符确实是有意义的)。 - syam
1
@syam:cplusplus和cppreference在返回类型上存在分歧。由于历史上cppreference更准确,我会相信你的说法。已修复。(严格来说,它不会是一个无限循环,而是会抛出std::bad_alloc异常,但你的观点仍然正确) - Mooing Duck
1
@MooingDuck 嗯,说实话我也没检查过这两个网站,我是从“man 3 strftime”中得到的,这也是一个相当安全的选择。 ;) 至于无限循环,那是我的错,措辞不当了。 - syam
显示剩余2条评论

4

使用 std::put_time() 的 C++11 解决方案:

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();
}

3

使用vector<char>作为缓冲区,而不是数组。重复增加大小,直到strftime返回非零值。


或者,由于他返回一个字符串,可以将该字符串用作缓冲区。 - Mooing Duck
只有当你有C++11,@Mooing。 - Rob Kennedy
没错,它只在C++11中有效。否则,vector<char>是更好的选择。 - Mooing Duck

2

我认为你最好的选择是提供一个固定的缓冲区,可以处理绝大多数情况,然后对剩余的情况进行特殊处理。类似于以下代码(未经测试,除了我脑袋里的湿件):

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函数如果提供的缓冲区不够大,将返回零。在这种情况下,您需要开始分配更大的缓冲区,直到它适合为止。

您可以根据需要调整未分配的缓冲区大小和分配大小的增量。该方法的优点是,在绝大多数情况下,您不会注意到效率降低(无论多小)。因为对于绝大多数情况,都不需要进行内存分配。

此外,您还可以选择其他方法(例如+10%,加倍等)来增加缓冲区大小。


或者,由于他正在返回一个字符串,可以将该字符串用作您的缓冲区。没有速度惩罚,并且代码更小。 - Mooing Duck

0

strftime()函数如果缓冲区的大小不足以容纳预期结果,则返回0。利用这个属性,您可以在堆上分配缓冲区,并尝试连续的2的幂作为其大小:1、2、4、8、16等,直到缓冲区足够大。使用2的幂的优点是解决方案的复杂度与结果的长度成对数比例。

还有一个需要考虑的特殊情况:格式可能是这样的,结果的大小总是为0(例如,空格式)。不确定如何处理。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接