(语言/ API:标准C 89库和/或POSIX)
可能是一个微不足道的问题,但我有一种感觉,我错过了一些东西。
我需要实现这个功能:
time_t get_local_midnight_timestamp(time_t ts);
也就是说,我们得到任意时间戳(例如,从去年开始),并将其四舍五入到同一天的午夜。
问题是该功能必须知道DST开关和DST规则的变化(如DST取消和/或扩展)。
该功能还必须面向未来,并应对奇怪的TZ变化(如提前30分钟的时区转移等)。
(我需要实现这一切的原因需要查看一些较旧的统计数据。)
据我了解,将struct tm
时间字段归零的天真方法不起作用 - 正是因为DST的东西(在DST更改日看起来有两个本地午夜time_t时间戳)。
请指出正确的方向......
我怀疑它可以用标准C 89完成,因此POSIX特定的解决方案是可以接受的。如果不是POSIX,那么Debian特有的东西会做......
更新:另外:有些东西告诉我,我也应该考虑闰秒。也许我应该考虑尝试直接使用Tz database ...(这是相当悲伤 - 如此多的/感知/开销这么小的任务。)......或者不 - 似乎libc应该使用它,所以也许我只是做错了......
更新2:这就是为什么我认为天真的解决方案不起作用的原因:
#include <stdio.h>
#include <time.h>
int main()
{
struct tm date_tm;
time_t date_start = 1301173200; /* Sunday 27 March 2011 0:00:00 AM MSK */
time_t midnight = 0;
char buf1[256];
char buf2[256];
int i = 0;
for (i = 0; i < 4 * 60 * 60; i += 60 * 60)
{
time_t date = date_start + i;
localtime_r(&date, &date_tm);
strftime(buf1, 256, "%c %Z", &date_tm);
date_tm.tm_sec = 0;
date_tm.tm_min = 0;
date_tm.tm_hour = 0;
midnight = mktime(&date_tm);
strftime(buf2, 256, "%c %Z", &date_tm);
printf("%d : %s -> %d : %s\n", (int)date, buf1, (int)midnight, buf2);
}
}
输出(我运行此时的本地时间是MSD):
$ gcc time.c && ./a.out 1301173200 : Sun Mar 27 00:00:00 2011 MSK -> 1301173200 : Sun Mar 27 00:00:00 2011 MSK 1301176800 : Sun Mar 27 01:00:00 2011 MSK -> 1301173200 : Sun Mar 27 00:00:00 2011 MSK 1301180400 : Sun Mar 27 03:00:00 2011 MSD -> 1301169600 : Sat Mar 26 23:00:00 2011 MSK 1301184000 : Sun Mar 27 04:00:00 2011 MSD -> 1301169600 : Sat Mar 26 23:00:00 2011 MSK
如你所见,两个中午。
答案 0 :(得分:5)
我将TZ
环境变量设置为“Europe / Moscow”运行您的代码,并且能够重现您的输出。以下是我的想法:
在前两行,一切都很好。然后我们“向前迈进”,凌晨2点变成凌晨3点。让我们使用gdb
在mktime
的入口处中断并查看每次的参数:
hour mday mon year wday yday isdst gmtoff tm_zone
0 27 2 111 0 85 0 10800 MSK
0 27 2 111 0 85 0 10800 MSK
0 27 2 111 0 85 1 14400 MSD
0 27 2 111 0 85 1 14400 MSD
那发生了什么?你的代码每次将小时设置为0,但是在DST切换之后这是一个问题,因为不可能发生了:它现在在DST开关之前就时间而言,但isdst现在已设置并且gmtoff已经增加了一个小时。通过乱砍时间,你已经“创建”了午夜的时间但启用了DST,这基本上是无效的。
你现在可能想知道,我们怎么能摆脱这个烂摊子?不要灰心! 当您手动调整tm_hour
字段时,只需将tm_isdst
设置为-1即可承认您不再知道DST状态。此特殊值,即记录在man localtime
中,表示DST状态为“不可用”。所以计算机会搞清楚,一切都应该正常。
这是我的代码补丁:
date_tm.tm_hour = 0;
+ date_tm.tm_isdst = -1; /* we no longer know if it's DST or not */
现在我得到了这个输出,我希望是你想要的:
$ TZ='Europe/Moscow' ./a.out
1301173200 : Sun Mar 27 00:00:00 2011 MSK -> 1301173200 : Sun Mar 27 00:00:00 2011 MSK
1301176800 : Sun Mar 27 01:00:00 2011 MSK -> 1301173200 : Sun Mar 27 00:00:00 2011 MSK
1301180400 : Sun Mar 27 03:00:00 2011 MSD -> 1301173200 : Sun Mar 27 00:00:00 2011 MSK
1301184000 : Sun Mar 27 04:00:00 2011 MSD -> 1301173200 : Sun Mar 27 00:00:00 2011 MSK