类似于strftime的合理snprintf替代方案?

8
是否有标准的(C、C++、POSIX、Linux等)替代strftime的函数可用于:
1. 计算给定格式和时间所需的字符串缓冲区大小, 2. 如果缓冲区大小不足以完全输出,则截断输出(而不像strftime一样使数组内容未定义)。
例如,接受strftime格式字符串的日期/时间格式化的类似snprintf的语义将非常完美。
像C++11及更高版本中的std :: put_time之类的函数不是一个选项,因为这些函数可能尝试动态分配额外的内存并可能引发异常。

2
假设有人会在接下来的7983年里更新你的代码,那么就数一下你需要用手指格式化时间的字节数。自动完成这项任务似乎并不是必要的。只需要避免使用过长的星期和月份名称,因为它们与语言环境有关。 - Art
不,没有,但你可以相对容易地编写自己的函数。stdarg.hvsnprintf可能有助于实现这一点。 - Akira
1
@Akira 嗯,当涉及到当前语言环境下的工作日和月份名称时,可能并不那么容易。由于strftime有固定数量的参数,因此不需要使用stdarg和vsnprintf进行替换。 - Ctx
64k 对你的堆栈来说根本不算什么。 - Michaël Roy
如果 ISO 8601 样式可以接受,那么您可以使用类似于 https://dev59.com/rmkw5IYBdhLWcg3w9_Qi 的方法。这样缓冲区大小就不会改变。 - Paul Floyd
显示剩余4条评论
1个回答

2
可以尝试使用更大的缓冲区,直到代码成功(或决定这太多了)。下面使用了VLA(不是C++),巧妙地避免了“尝试动态分配额外内存”的问题 - 眨眨眼。简单地分配一个大缓冲区,比如char buf[J_STRFTIME_MAX],对于实际编码应该足够了。@Michaël Roy并避免迭代方法。
#include <stdio.h>
#include <time.h>
#define J_STRFTIME_MAX 100

size_t j_strftime(char * s, size_t maxsize, const char * fmt, const struct tm * t) {
  size_t sz = strftime(s, maxsize, fmt, t);
  if (sz) {
    return sz;
  }
  size_t new_size = maxsize ? maxsize : 1;
  do {
    new_size *= 2;
    char new_s[new_size];
    sz = strftime(new_s, sizeof new_s, fmt, t);
    if (sz) {
      s[0] = 0;
      // strncat(s, new_s, maxsize);
      strncat(s, new_s, maxsize - 1);
      return strlen(new_s);
    }
  } while (sz < J_STRFTIME_MAX/2);
  return 0;
}

int main() {
  time_t now;
  time(&now);
  struct tm tm = *gmtime(&now);
  for (size_t i = 1; i < 30; i += 3) {
    char s[i];
    size_t sz = j_strftime(s, sizeof s, "%c", &tm);
    printf("%2zu %2zu <%s>\n", i, sz, s);
  }
}

输出

 1 24 <T>
 4 24 <Thu >
 7 24 <Thu Jul>
10 24 <Thu Jul  6>
13 24 <Thu Jul  6 14>
16 24 <Thu Jul  6 14:45>
19 24 <Thu Jul  6 14:45:00>
22 24 <Thu Jul  6 14:45:00 20>
25 24 <Thu Jul  6 14:45:00 2017>
28 24 <Thu Jul  6 14:45:00 2017>

非迭代的
size_t j_strftime2(char * s, size_t maxsize, const char * fmt, const struct tm * t) {
  size_t sz = strftime(s, maxsize, fmt, t);
  if (sz == 0) {
    char new_s[J_STRFTIME_MAX];
    sz = strftime(new_s, sizeof new_s, fmt, t);
    if (sz == 0) {
      return 0;  // Too too big
    }
    s[0] = 0;
    // strncat(s, new_s, maxsize);
    strncat(s, new_s, maxsize - 1);
  }
  return sz;
}

【编辑】代码已经修正。

为避免不必要的迭代,您可以从更大的 new_size 值开始:size_t new_size = maxsize > 128 ? maxsize : 128; - chqrlie
@chqrlie 确实。一个不错的直接方法。然而,OP似乎对内存使用有所担忧,因此迭代方法逐渐出现。 - chux - Reinstate Monica
为什么不在所有情况下返回sz,以实现与snprintf相同的语义呢?用户最多只需在调用者级别上分配最小大小的内存即可完成2个步骤。 - chqrlie
@chqrlie同意返回所需的大小(不包括\0)。代码已修改。 - chux - Reinstate Monica
一个无效的指定符,一个无效的 tm 结构,多字节编码错误... 你可以通过设置 new_size 的任意最大值并在达到时返回 0 来处理这些问题。 - chqrlie
显示剩余4条评论

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