Ale*_*ysh 2 c unix-timestamp dst
(语言/ API:标准C 89库和/或POSIX)
可能是一个微不足道的问题,但我有一种感觉,我错过了一些东西.
我需要实现这个功能:
time_t get_local_midnight_timestamp(time_t ts);
Run Code Online (Sandbox Code Playgroud)
也就是说,我们得到任意时间戳(例如,从去年开始),并将其四舍五入到同一天的午夜.
问题是该功能必须知道DST开关和DST规则的变化(如DST取消和/或扩展).
该功能还必须面向未来,并应对奇怪的TZ变化(如提前30分钟的时区转移等).
(我需要实现所有这些的原因需要查看一些较旧的统计数据.)
根据我的理解,struct tm将时间字段归零的天真方法不起作用 - 正是因为DST的东西(在DST更改日看起来有两个本地午夜time_t时间戳).
请指出我正确的方向......
我怀疑它可以用标准C 89完成,因此POSIX特定的解决方案是可以接受的.如果不是POSIX,那么Debian特有的东西会做......
更新:另外:有东西告诉我,我也应该考虑闰秒.也许我应该考虑尝试直接使用Tz数据库 ...(这是相当悲伤 - 如此多的/感知/开销这么小的任务.)...或者不 - 似乎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);
}
}
Run Code Online (Sandbox Code Playgroud)
输出(我运行此时的本地时间是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
如你所见,两个中午.
我运行你的代码,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
Run Code Online (Sandbox Code Playgroud)
那发生了什么?你的代码每次将小时设置为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 */
Run Code Online (Sandbox Code Playgroud)
现在我得到这个输出,我希望是你想要的:
$ 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
Run Code Online (Sandbox Code Playgroud)