结构体timeval转换为可打印格式

31

请问如何将 struct timeval 格式化为人类可读的格式,例如 "2010-01-01 15:35:10.0001"?


2
重复的问题:https://dev59.com/tnM_5IYBdhLWcg3wPAZF? - David Gelhar
9
这不是一份重复内容,因为这个指定了可读性的格式并且引用了strftime函数,而另一个则只是提取秒和微秒。 - Joe Hildebrand
7个回答

75

由于在strftime()中处理的struct tm中不存在微秒部分,因此您需要手动添加。以下是一个片段:

struct timeval tv;
time_t nowtime;
struct tm *nowtm;
char tmbuf[64], buf[64];

gettimeofday(&tv, NULL);
nowtime = tv.tv_sec;
nowtm = localtime(&nowtime);
strftime(tmbuf, sizeof tmbuf, "%Y-%m-%d %H:%M:%S", nowtm);
snprintf(buf, sizeof buf, "%s.%06ld", tmbuf, tv.tv_usec);

注意我们如何使用显式精度06来获得填充了零的微秒字段。由于微秒从0到999,999,它必须始终填充为6个数字。我们不想将57微秒误传为570000(比较“1.57”和“1.000057”)。


2
这可以通过保存strftime的返回值(它返回字节数)并使用它来索引相同的缓冲区来更有效地完成,然后可以将其传递给snprintf。这将利用一个缓冲区并避免从strftime()复制字符串的额外副本。 - Nathan Doromal
2
非常感谢您的回答,以及整个讨论串的内容。作为补充,在最后一行中,或许最好指定为 ",%s.%06ld"。祝好! - lrleon
1
我收到一个警告:格式“%d”期望类型为“int”的参数,但是参数5的类型为“__suseconds_t {aka long int}” [-Wformat =] 在最后一行上,有什么方法可以摆脱它? - Lightsout
@bakalolo 很好的发现,我忘记了它是“long”。已经修复,谢谢! - unwind
1
snprintf(buf, sizeof buf, "%s.%06ld", tmbuf, tv.tv_usec)会产生编译警告/错误,如下所示:
'%06ld' 指令输出可能被截断,在大小为0到63之间的区域中写入6到20个字节[-Werror=format-truncation=]
- leiyc

14

使用localtimestrftimetv_sec转换,然后添加tv_usec部分。


5

将之前的回答和评论结合起来,将格式更改为RFC3339兼容,并检查所有错误条件,您会得到以下结果:

#include <stdio.h>
#include <sys/time.h>

ssize_t format_timeval(struct timeval *tv, char *buf, size_t sz)
{
  ssize_t written = -1;
  struct tm *gm = gmtime(&tv->tv_sec);

  if (gm)
  {
    written = (ssize_t)strftime(buf, sz, "%Y-%m-%dT%H:%M:%S", gm);
    if ((written > 0) && ((size_t)written < sz))
    {
      int w = snprintf(buf+written, sz-(size_t)written, ".%06dZ", tv->tv_usec);
      written = (w > 0) ? written + w : -1;
    }
  }
  return written;
}

int main() {
  struct timeval tv;
  char buf[28];
  if (gettimeofday(&tv, NULL) != 0) {
    perror("gettimeofday");
    return 1;
  }
  if (format_timeval(&tv, buf, sizeof(buf)) > 0) {
    printf("%s\n", buf);
    // sample output:
    // 2015-05-09T04:18:42.514551Z
  }
  return 0;
}

3
ctime((const time_t *) &timeval.ts.tv_sec)

我想您正在寻找此代码,仅供参考。


请注意,ctime()asctime()返回的值格式(例如“Wed Jun 30 21:49:08 1993\n”)与OP的要求不匹配,因为它们不包括小数秒,并且甚至没有最后一秒,因此没有简单的方法在之后附加小数秒。 - John Hascall
注意,我同意@JohnHascall上面的评论,这并没有解决所述的问题。我将在此处留下答案,以便其他人不会被诱导以这种方式解决它。 - Joe Hildebrand

1

这是我使用的:

#include <time.h>
#include <string.h>

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#define gmtime_r(ptime,ptm) (gmtime_s((ptm),(ptime)), (ptm))
#else
#include <sys/time.h>
#endif

#define ISO8601_LEN (sizeof "1970-01-01T23:59:59.123456Z")

char *timeval_to_str(char iso8601[restrict static ISO8601_LEN], unsigned precision, const struct timeval * restrict tv) {
    struct tm tm;
    if (!gmtime_r(&tv->tv_sec, &tm))
        return memcpy(iso8601, "Error: Year overflow", sizeof "Error: Year overflow");

    tm.tm_year %= 10*1000;
    char *frac = iso8601 + strftime(iso8601, sizeof "1970-01-01T23:59:59.", "%Y-%m-%dT%H:%M:%SZ", &tm);

    if (precision) {
        unsigned long usecs = tv->tv_usec;
        for (int i = precision; i < 6; i++) usecs /= 10;
        char *spaces = frac + sprintf(frac - 1, ".%-*luZ", precision, usecs) - 3;
        if (spaces > frac) while (*spaces == ' ') *spaces-- = '0';
    }

    return iso8601;
}

precision 指定了秒的小数部分的宽度。代码是 y10k- 和 yINT_MAX- 证明的。


1

使用localtime_s而不是localtime来转换tv_sec,因为如果您正在编写全局函数,则可能会导致一些问题。如果您的功能可能在多线程解决方案中起作用,请考虑使用localtime_r


1

您可以使用strftime函数将日期和时间转换为字符串。


1
strftime函数的时间参数应该是“struct tm”,而不是“struct timeval”。 - Zoey Hewll

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