提问人:InterLinked 提问时间:6/8/2023 更新时间:6/8/2023 访问量:66
将 UTC 偏移量的日期时间字符串解析为 time_t
Parsing datetime string with UTC offset to time_t
问:
这个问题在这篇文章中有所暗示,但这个问题的答案根本没有回答这个问题,而且我到处散布着相互矛盾的建议和提示。
我的问题相对简单,但在深入研究时,我有点被绊倒了。
假设我有一个格式如下的字符串:2023-06-07 03:04:56 -0700
目标是将其规范化为纪元时间戳(在 C 中)。我以为这很简单,但似乎不是。这里的问题似乎是最后的。time_t
-0700
似乎忽略了修改后的,可能,也许(同样,关于如何使用它,在不同的实现中等,我有相互矛盾的报告)。FWIW,我使用的是 Linux/glibc,所以我更关心它是否在那里工作,而不是它不在 C 标准中。strptime(3)
%z
稍微玩了一下,在我看来,它确实忽略了时区偏移量。中的小时只是字符串中的小时。小时根本不会根据时区偏移量进行修改。据说这就是非标准成员的用途,但是在阅读时,我似乎得到了一个巨大的值,这绝对比任何UTC偏移量都要大得多,所以我也不确定该怎么做。strptime
struct tm
tm_gmoff
举个例子:
#define _XOPEN_SOURCE
#include <stdio.h>
#include <string.h>
#include <time.h>
int main()
{
struct tm tm;
time_t epoch;
char buf[40];
strcpy(buf, "2023-06-07 03:04:56 -0700");
memset(&tm, 0, sizeof(tm));
strptime(buf, "%Y-%m-%d %H:%M:%S %z", &tm);
printf("Parsed datetime %s (hour %d, offset %lu)\n", buf, tm.tm_hour, tm.__tm_gmtoff);
tm.tm_isdst = -1;
setenv("TZ", "US/Eastern");
epoch = mktime(&tm);
printf("Parsed datetime -> epoch %lu\n", epoch); // 7:04AM UTC
epoch = timegm(&tm);
printf("Parsed datetime -> epoch %lu\n", epoch); // 3:04AM UTC
return 0;
}
在 https://www.onlinegdb.com/online_c_compiler 上运行时,提供:
Parsed datetime 2023-06-07 03:04:56 -0700 (hour 3, offset 18446744073709526416)
Parsed datetime -> epoch 1686121496
Parsed datetime -> epoch 1686107096
请注意,字符串中的偏移量是任意的,系统上的本地时区也是任意的。例如,是太平洋时间,但系统可能是东部时间,这实际上与问题完全无关(即,在转换中不应使用本地时区,因为它无关紧要 - 应使用偏移量的时区 - 重要的是,本地时区不应混淆答案)。-0700
-0700
上面,正确答案是 UTC 时间上午 10:04(字符串显然应该转换为什么)。盲目使用给出错误的答案,甚至更不对劲。问题似乎是这里没有考虑偏移量。如果 为偏移量添加了 +7 小时,或者如果根据 中的某个内容(例如 tm_gmtoff)向答案添加了 +7 小时,则使用的第二个答案将是正确的。但这些事情似乎都没有发生。mktime
timegm
timegm
struct tm
timegm
struct tm
除了编写手动函数来解析时间字符串中并手动将此偏移量添加到 ,有没有更好的“内置”方法来使用标准函数执行此操作?(可移植性在这里并不是特别重要,只要它在 .鉴于这似乎是一种非常常见的转换类型,我认为一定有一种方法可以正确地做到这一点,而无需手动进行计算,使用 .我以为这就是目的,但似乎不是这样——我在这里错过了什么吗?%z
time_t
glibc
gmtime
tm_gmtoff
答:
几个问题......
- 签名 [因此打印不正确]
__tm_gmtoff
%lu
__tm_gmtoff
设置正确(例如)。-7 * 3600
- 做是行不通的。它使用系统设置的本地时区。(例如,-0700 是美国/太平洋(?)夏令时,但我得到了 -0400(美国/东部夏令时)。
setenv("TZ",...)
timegm
将忽略__tm_gmtoff
- 在 linux/glibc 上,符号是 [AFAICT]。
tm_gmtoff
- 最好手动使用和应用以获得正确的时区。
timegm
tm_gmtoff
这是一些更正的代码(分阶段)。它可能仍然被打破。请务必阅读评论:
//#define _XOPEN_SOURCE
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
void
sepline(const char *tag)
{
printf("\n");
for (int col = 1; col <= 80; ++col)
putchar('-');
printf("\n");
printf("%s:\n",tag);
printf("\n");
}
void
tmshow(const struct tm *tm,const char *tag)
{
printf("TMX: %4.4d/%2.2d/%2.2d-%2.2d:%2.2d:%2.2d (%ld/%ld) (from %s)\n",
tm->tm_year + 1900,tm->tm_mon + 1,tm->tm_mday,
tm->tm_hour,tm->tm_min,tm->tm_sec,tm->tm_gmtoff,tm->tm_gmtoff / 3600,
tag);
}
void
todshow(time_t tod,int gmtflg,const char *tag)
{
struct tm tm;
if (gmtflg)
gmtime_r(&tod,&tm);
else
localtime_r(&tod,&tm);
printf("\n");
printf("TOD: %ld (from %s)\n",tod,tag);
tmshow(&tm,tag);
}
void
orig(const char *buf)
{
struct tm tm;
memset(&tm, 0, sizeof(tm));
sepline("ORIG");
printf("BUF: %s\n",buf);
strptime(buf, "%Y-%m-%d %H:%M:%S %z", &tm);
printf("Parsed datetime %s (hour %d, offset %lu)\n",
buf, tm.tm_hour, tm.tm_gmtoff);
tm.tm_isdst = -1;
setenv("TZ", "US/Eastern", 1);
tmshow(&tm,"strptime");
time_t epoch_mktime = mktime(&tm);
printf("Parsed mktime -> epoch %lu\n", epoch_mktime); // 7:04AM UTC
time_t epoch_timegm = timegm(&tm);
printf("Parsed timegm -> epoch %lu\n", epoch_timegm); // 3:04AM UTC
time_t diff = epoch_mktime - epoch_timegm;
printf("diff = %ld (%.3f)\n",diff,diff / 3600.0);
}
void
fix1(const char *buf)
{
struct tm tm;
memset(&tm, 0, sizeof(tm));
sepline("FIX1");
printf("BUF: %s\n",buf);
strptime(buf, "%Y-%m-%d %H:%M:%S %z", &tm);
#if 0
printf("Parsed datetime %s (hour %d, offset %ld/%ld)\n",
buf, tm.tm_hour, tm.tm_gmtoff, tm.tm_gmtoff / 3600);
#endif
tm.tm_isdst = -1;
//setenv("TZ", "US/Eastern", 1);
unsetenv("TZ");
tmshow(&tm,"strptime");
time_t epoch_mktime = mktime(&tm);
//printf("Parsed mktime -> epoch %lu\n", epoch_mktime); // 7:04AM UTC
todshow(epoch_mktime,0,"mktime");
time_t epoch_timegm = timegm(&tm);
//printf("Parsed timegm -> epoch %lu\n", epoch_timegm); // 3:04AM UTC
todshow(epoch_timegm,1,"timegm");
time_t diff = epoch_mktime - epoch_timegm;
printf("diff = %ld (%.3f)\n",diff,diff / 3600.0);
}
void
fix2(const char *buf)
{
struct tm tm;
memset(&tm, 0, sizeof(tm));
sepline("FIX2");
printf("BUF: %s\n",buf);
strptime(buf, "%Y-%m-%d %H:%M:%S %z", &tm);
tmshow(&tm,"strptime");
// NOTE: timegm ignores this -- so remember it
time_t offset = tm.tm_gmtoff;
//tm.tm_gmtoff = 0;
time_t epoch_timegm = timegm(&tm);
todshow(epoch_timegm,1,"timegm");
// adjust for timezone -- this produces correct GMT
todshow(epoch_timegm - offset,1,"timegm+offset");
// NOTE/BUG: setting TZ does _not_ work
#if 0
time_t epoch_mktime = epoch_timegm;
epoch_mktime -= offset;
setenv("TZ", "US/Pacific", 1);
localtime_r(&epoch_mktime,&tm);
#endif
#if 1
time_t epoch_mktime = epoch_timegm;
//epoch_mktime += offset;
//epoch_mktime += offset;
gmtime_r(&epoch_mktime,&tm);
tm.tm_gmtoff += offset;
//tm.tm_gmtoff += offset;
#endif
//printf("Parsed mktime -> epoch %lu\n", epoch_mktime); // 7:04AM UTC
tmshow(&tm,"localtime_r");
time_t diff = epoch_mktime - epoch_timegm;
printf("diff = %ld (%.3f)\n",diff,diff / 3600.0);
}
int
main()
{
char buf[40];
// Pacific time???
strcpy(buf, "2023-06-07 03:04:56 -0700");
orig(buf);
fix1(buf);
fix2(buf);
return 0;
}
以下是程序输出:
--------------------------------------------------------------------------------
ORIG:
BUF: 2023-06-07 03:04:56 -0700
Parsed datetime 2023-06-07 03:04:56 -0700 (hour 3, offset 18446744073709526416)
TMX: 2023/06/07-03:04:56 (-25200/-7) (from strptime)
Parsed mktime -> epoch 1686121496
Parsed timegm -> epoch 1686107096
diff = 14400 (4.000)
--------------------------------------------------------------------------------
FIX1:
BUF: 2023-06-07 03:04:56 -0700
TMX: 2023/06/07-03:04:56 (-25200/-7) (from strptime)
TOD: 1686121496 (from mktime)
TMX: 2023/06/07-03:04:56 (-14400/-4) (from mktime)
TOD: 1686107096 (from timegm)
TMX: 2023/06/07-03:04:56 (0/0) (from timegm)
diff = 14400 (4.000)
--------------------------------------------------------------------------------
FIX2:
BUF: 2023-06-07 03:04:56 -0700
TMX: 2023/06/07-03:04:56 (-25200/-7) (from strptime)
TOD: 1686107096 (from timegm)
TMX: 2023/06/07-03:04:56 (0/0) (from timegm)
TOD: 1686132296 (from timegm+offset)
TMX: 2023/06/07-10:04:56 (0/0) (from timegm+offset)
TMX: 2023/06/07-03:04:56 (-25200/-7) (from localtime_r)
diff = 0 (0.000)
评论
time_t epoch_mktime ... printf("Parsed mktime -> epoch %lu\n", epoch_mktime);
TZ
tzset()
评论
char *
strptime()
'\0'