DST-switch-aware getter for UNIX timestamp of current day's local time midnight
(Language/API: Standard C 89 library and / or POSIX)
Probably a trivial question, but I've got a feeling that I'm missing something.
I need to implement this function:
time_t get_local_midnight_timestamp(time_t ts);
That is, we get arbitrary timestamp (from the last year, for example), and return it rounded up to the midnight of the same day.
The problem is that the function must be aware of DST switches and DST rules changes (like DST cancellation and/or extension).
The function must also be future-proof, and cope with weird TZ changes (like shift of time zone 30 minutes ahead etc.).
(The reason I need all this that I need to implement look up into some older statistics data.)
As far as I understand, naïve approach with zeroing out struct tm
time fields would not work — precisely because of DST stuff (looks like in DST-change day there are two local midnight time_t timestamps).
Please point me in the right direction...
I doubt that it can be done with standard C 89, so POSIX-specific solutions are acceptable. If not POSIX, then something Debian-specific would do...
Update: Also: Something tel开发者_运维知识库ls me that I should also take leap seconds in account. Maybe I should look into trying to directly use Tz database... (Which is rather sad — so much /perceived/ overhead for so small task.) ...Or not — seems that libc should use it, so maybe I'm just doing it wrong...
Update 2: Here is why I think that naïve solution does not work:
#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);
}
}
Output (local time was MSD at the moment when I run this):
$ 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
As you can see, two midnights.
I ran your code with the TZ
environment variable set to "Europe/Moscow" and was able to reproduce your output. Here's what I think is going on:
On the first two lines, everything is fine. Then we "spring ahead" and 2 AM becomes 3 AM. Let's use gdb
to break on entry to mktime
and see what its argument is each time:
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
So what has happened? Your code sets the hour to 0 each time, but this is a problem after the DST switch, because the impossible has happened: it is now "before" the DST switch in terms of the time of day, yet isdst is now set and gmtoff has been increased by one hour. By hacking up the time, you have "created" a time of midnight but with DST enabled, which is basically invalid.
You may now wonder, how can we get out of this mess? Do not despair! When you are adjusting the tm_hour
field by hand, simply admit that you no longer know what the DST status is by setting tm_isdst
to -1. This special value, which is documented in man localtime
, means the DST status is "not available." So the computer will figure it out, and everything should work fine.
Here's my patch for your code:
date_tm.tm_hour = 0;
+ date_tm.tm_isdst = -1; /* we no longer know if it's DST or not */
Now I get this output, I hope is what you want:
$ 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
精彩评论