another one question regarding mktime and DST
Linux, Ubuntu, time zone is set to Europe/Berlin i.e. current time is CEST:
>date
Mon Aug 22 16:08:10 CEST 2016
>date --utc
Mon Aug 22 14:08:14 UTC 2016
everything okay so far.
Now I try to run the following code:
#include <stdio.h>
#include <time.h>
int main()
{
struct tm tm = {0};
int secs;
tm.tm_sec = 0;
tm.tm_min = 0;
tm.tm_hour = 12;
tm.tm_mon = 9 - 1;
tm.tm_mday = 30;
tm.tm_year = 2016 - 1900;
tm.tm_isdst = 0;
secs = mktime(&tm);
printf("%i\n", secs);
tm.tm_isdst = 1;
secs = mktime(&tm);
printf("%i\n", secs);
tm.tm_isdst = -1;
secs = mktime(&tm);
printf("%i\n", secs);
return 0;
}
and get
1475233200
1475233200
1475233200
which is in all three cases wrong (1 hour offset):
>date -d @1475233200
Fri Sep 30 13:00:00 CEST 2016
So I am a bit puzzled now, is my timezone somehow broken? Why is tm_isdst flag ignored completely?
Edit: @Nominal Animal had the answer: mktime modifies tm_hour! I wonder where it is documented?!
#include <stdio.h>
#include <time.h>
void reset(struct tm* tm){
(*tm) = (const struct tm){0};
tm->tm_sec = 0;
tm->tm_min = 0;
tm->tm_hour = 12;
tm->tm_mon = 9 - 1;
tm->tm_mday = 30;
tm->tm_year = 2016 - 1900;
}
int main()
{
struct tm tm;
int secs;
reset(&tm);
tm.tm_isdst = 0;
secs = mktime(&tm);
printf("%i\n", secs);
reset(&tm);
tm.tm_isdst = 1;
secs = mktime(&tm);
printf("%i\n", secs);
reset(&tm);
tm.tm_isdst = -1;
secs = mktime(&tm);
printf("%i\n", secs);
return 0;
}
gives
1475233200
1475229600
1475229600
I think I can now see how one would find this confusing. Think of mktime()
as having signature
time_t mktime_actual(struct tm *dst, const struct tm *src);
where the time_t
result is calculated based on (normalized) *src
, and the normalized fields and whether daylight savings time applies at that time, is saved to *dst
.
It is just that the C language developers historically chose to use only one pointer, combining both src
and dst
. The above logic still stands, though.
See the `man mktime man page, especially this part:
The mktime() function converts a broken-down time structure,
expressed as local time, to calendar time representation. The
function ignores the values supplied by the caller in the tm_wday and
tm_yday fields. The value specified in the tm_isdst field informs
mktime() whether or not daylight saving time (DST) is in effect for
the time supplied in the tm structure: a positive value means DST is
in effect; zero means that DST is not in effect; and a negative value
means that mktime() should (use timezone information and system
databases to) attempt to determine whether DST is in effect at the
specified time.
The mktime() function modifies the fields of the tm structure as
follows: tm_wday and tm_yday are set to values determined from the
contents of the other fields; if structure members are outside their
valid interval, they will be normalized (so that, for example, 40
October is changed into 9 November); tm_isdst is set (regardless of
its initial value) to a positive value or to 0, respectively, to
indicate whether DST is or is not in effect at the specified time.
Calling mktime() also sets the external variable tzname with
information about the current timezone.
If the specified broken-down time cannot be represented as calendar
time (seconds since the Epoch), mktime() returns (time_t) -1 and does
not alter the members of the broken-down time structure.
In other words, if you change your test program a bit, say into
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
static const char *dst(const int flag)
{
if (flag > 0)
return "(>0: is DST)";
else
if (flag < 0)
return "(<0: Unknown if DST)";
else
return "(=0: not DST)";
}
static struct tm newtm(const int year, const int month, const int day,
const int hour, const int min, const int sec,
const int isdst)
{
struct tm t = { .tm_year = year - 1900,
.tm_mon = month - 1,
.tm_mday = day,
.tm_hour = hour,
.tm_min = min,
.tm_sec = sec,
.tm_isdst = isdst };
return t;
}
int main(void)
{
struct tm tm = {0};
time_t secs;
tm = newtm(2016,9,30, 12,0,0, -1);
secs = mktime(&tm);
printf("-1: %04d-%02d-%02d %02d:%02d:%02d %s %lld\n",
tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec, dst(tm.tm_isdst), (long long)secs);
tm = newtm(2016,9,30, 12,0,0, 0);
secs = mktime(&tm);
printf(" 0: %04d-%02d-%02d %02d:%02d:%02d %s %lld\n",
tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec, dst(tm.tm_isdst), (long long)secs);
tm = newtm(2016,9,30, 12,0,0, 1);
secs = mktime(&tm);
printf("+1: %04d-%02d-%02d %02d:%02d:%02d %s %lld\n",
tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec, dst(tm.tm_isdst), (long long)secs);
return EXIT_SUCCESS;
}
then running it produces output
-1: 2016-09-30 12:00:00 (>0: is DST) 1475226000
0: 2016-09-30 13:00:00 (>0: is DST) 1475229600
+1: 2016-09-30 12:00:00 (>0: is DST) 1475226000
In other words, it behaves exactly as described (in the quote above). This behaviour is documented in C89, C99, and POSIX.1 (I think C11 also, but haven't checked).
On successful completion, the values of the tm_wday
and tm_yday
components of the structure are set appropriately, and the other components are set to represent the specified calendar
time, ... C11dr §7.27.2.3 2
When calling mktime(&tm)
, the original values of tm
are not range restricted.
Because of the the first mktime(&tm)
call, certainly tm.tm_isdst
and tm.tm_hour
were adjusted to 1 and 11. So OP's following code tm.tm_isdst = 1;
and tm.tm_isdst = -1;
did not affect the timestamp.
Better to set all fields to investigate.
struct tm tm0 = {0};
struct tm tm;
int secs;
tm0.tm_sec = 0;
tm0.tm_min = 0;
tm0.tm_hour = 12;
tm0.tm_mon = 9 - 1;
tm0.tm_mday = 30;
tm0.tm_year = 2016 - 1900;
tm = tm0;
tm.tm_isdst = 0;
secs = mktime(&tm);
printf("%i\n", (int) secs);
tm = tm0;
tm.tm_isdst = 1;
secs = mktime(&tm);
printf("%i\n", (int) secs);
tm = tm0;
tm.tm_isdst = -1;
secs = mktime(&tm);
printf("%i\n", (int) secs);