将自纪元以来的秒数转换为time_t

3

我有一个表示自纪元以来经过的秒数的字符串,我需要将其转换为可读性强的字符串。我在网上看到了一些帖子建议简单地将整数转换为time_t,如下所示:

time_t time = (time_t)(atoi(secs_since_epoch_str));

但是,如果我查找time_t的定义:

typedef /* unspecified */ time_t;

Although not defined by the C standard, this is almost always an integral 
value holding the number of seconds (not counting leap seconds) since 00:00,
Jan 1 1970 UTC, corresponding to POSIX time.

所以,这并不是保证可行的。我想知道是否有正确的方法来做到这一点?


3
使用atoi进行整数转换后再进行强制类型转换是没有意义的。最好使用strtoull将其转换为unsigned long long,然后再将其转换为time_t - Weather Vane
@WeatherVane 我同意你的观点,但是OP说这些是自纪元以来的秒数。也许在他们的用例中,经过的时间从未超过2^32秒。 - Fra93
2
@Fra93 说得好,但我们应该从千年虫的教训中学到,代码应该是面向未来的。这将在2038年溢出int,这并不是那么遥远。 - Weather Vane
1
一个 time_t 值是“自纪元以来的秒数”,所以你的问题更多地是“是否有一种方法将自纪元以来的秒数的字符串表示转换为 time_t 值”。而答案是“没有 - 没有一个函数明确地接受自纪元以来的秒数的字符串表示,并返回一个 time_t 值”。 - Jonathan Leffler
请使用strtol代替atoi(),参见为什么不应该使用atoi()? - phuclv
显示剩余6条评论
2个回答

2
将“自纪元以来的秒数”转换为“time_t” 我有一个表示“自纪元以来的秒数”的字符串,我需要将其转换为人类可读的字符串。
将“秒”偏移量添加到时代“struct tm”的.tm_sec成员中,并形成time_t
将字符串转换为宽整数。(这里假设该字符串表示完整的秒数。)它可能比long更宽。出于简洁起见,省略了错误处理。
    long long offset = strtoll(string, NULL, 10);
    // or pedantically
    intmax_t offset = strtoimax(string, NULL, 10);

考虑到time_t不一定是秒数(例如,可能是纳秒),而且时代也可能不是1970年1月1日,这更有趣。为时代形成一个时间戳。
    time_t epoch = 0;
    struct tm *tm_ptr = localtime(&epoch);
    if (tm_ptr == NULL) Handle_Error();
    struct tm tm = *tm_ptr;
  1. struct tm 成员添加。可以将偏移量的加法分成若干部分,以避免 int 溢出。当 int 是 16 位且 offset 很大时(例如 88+ 年),需要更多的代码。
    tm.tm_sec += offset%60;
    offset /= 60;
    tm.tm_min += offset%60;
    offset /= 60;
    tm.tm_hour += offset%24;
    offset /= 24;
    tm.tm_mday += offset;
  1. 调用mktime()将时间戳转换为主要范围并打印输出。请注意,由于time_t可能是某种整数类型或某种浮点类型,因此需要将其转换为浮点类型进行打印。
    time_t target = mktime(&tm);
    printf("%Lg\n", (long double) target);
    if (target == -1) Handle_Error();
    printf("%s", asctime(&tm));

1
所以,这并不保证能够正常工作。我想知道有没有更妥善的方法?
嗯...我不完全明白你上面的结论是什么。time_t是一个unix特定类型,已经发展为POSIX标准。如果你收到它作为一个字符串,那么你必须处理它可能是一个64位数字的可能性(例如:FreeBSD使用64位的time_t)。
关于纪元时间,它并不总是那个纪元时间(unix的早期版本使用1972年1月1日,但我想我们中没有人见过这样的系统——我在家里运行着一个pdp-11的unix v7,并且它有32位的时间——那时候没有time_t类型,它是一个long int类型——并且它是基于实际的纪元时间)。
你必须小心,因为自从它的创造以来,unix时间一直是一个带符号的值,因此它从1970年1月1日开始,但你可以指定日期在那之前,只需使用负值即可,如下所示:
$ LANG=C date -u -r -2147483648
Fri Dec 13 20:45:52 UTC 1901
$ _

这就是为什么它会耗尽

$ LANG=C date -u -r 2147483647
Tue Jan 19 03:14:07 UTC 2038
$ _

在另一方面,你的问题不明确,因为你没有说明最终字符串数据要转换成哪个地区和时区(或格式)。最好使用标准的Unix时间函数(查找gmtime(3)localtime(3)和与之关联的struct tm类型)。
一个常见的方法是:
    time_t now = time(NULL);
    struct tm *now_gmt = gmtime(&now);

然后,如果您对时间的特定格式不感兴趣,可以使用:

    printf("%s", asctime(now_gmt));

或者您可以操作 struct tm 的字段 (tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec, tm_wday, tm_ydaytm_isdst ---最后一个用于知道您是否处于夏季或冬季时间)

因此,在 POSIX 中所有问题都得到了解决。

最后,如果您有一个大量(64 位值)需要转换为 time_t,请使用以下方法进行转换:

#include <stdint.h>

...

    int64_t val;

    sscanf(the_string_value, "%lld", &val); /* use a 64bit type */
    time_t my_time_t = (time_t) val; /* then cast it to time_t */

我使用了强制类型转换,因为time_t可能比int64_t小,这可以避免编译器发出警告。

这将适用于64位和32位整数。一旦你将其转换为time_t,所有上述内容仍然有效。


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