您可以使用简单但非标准的timegm() 函数,而不是使用strftime() 将分解时间struct tm 格式化为整数并使用atoi() 解析它。
尽管timegm() 不在 POSIX 中,但大多数 POSIXy 系统都提供了它。如果您希望您的代码可跨 POSIXy 系统移植,您可以使用一种解决方法(如 timegm 手册页的某些版本中所述):
#define _POSIX_C_SOURCE 200809L
#define _XOPEN_SOURCE
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
time_t alt_timegm(struct tm *from)
{
char *tz;
time_t result;
int saved_errno;
tz = getenv("TZ");
if (tz)
tz = strdup(tz);
setenv("TZ", "", 1); /* TZ empty refers to UTC */
tzset();
errno = 0;
result = mktime(from);
saved_errno = errno;
setenv("TZ", tz, 1);
free(tz);
errno = saved_errno;
return result;
}
这种解决方法确实有一个缺点,它会临时修改当前时区,这会影响执行任何时区相关函数的其他线程。在单线程进程中,这不是问题(因为与时区相关的函数不是异步信号安全的,因此它们不能在信号处理程序中使用)。
在多线程程序中,应该序列化对时区相关函数的所有访问——上面的alt_timegm()、mktime()、localtime() 等等——以确保每个函数都以正确的时区集运行。
Linux 和 BSD 中的 timegm() 实现是线程安全的;也就是说,它们不会修改时区。
使用上述方法,将使用strptime() 格式的字符串解析为 Unix 时间戳很容易:
const char *parse_utc(const char *s, const char *format, time_t *to)
{
char *result;
struct tm t_parts;
time_t t;
if (!s || !format) {
errno = EINVAL;
return NULL;
}
result = strptime(s, format, &t_parts);
if (!result) {
errno = EINVAL;
return NULL;
}
errno = 0;
t = alt_timegm(&t_parts);
if (to)
*to = result;
return (const char *)result;
}
上面的parse_utc()使用strptime()格式解析一个字符串sformat,如果不为NULL,则将UTC时间戳保存到to,返回指向字符串s中第一个未解析字符的指针。
在上述函数中设置errno = 0 可能看起来很奇怪,但这是因为mktime()(和timegm())函数可能会或可能不会设置errno以防出错;它只是返回(time_t)-1。这意味着无法可靠地确定 (time_t)-1(对应于 UTC 中 1969 年的最后一秒)是实际有效的时间戳,还是错误返回。
换句话说,如果errno != 0 在调用parse_utc() 之后,就会出现错误。 (注意,如果字符串或格式无效,parse_utc() 返回NULL,与errno == EINVAL。)如果errno == 0 但*to == (time_t)-1,这取决于架构时间字符串是否实际引用的最后一秒UTC 中的 1969 年,或者如果这是一个错误;我们根本不知道。